itertools或手写的发电机 - 什么是更好的?

时间:2010-10-03 12:28:44

标签: python iterator generator

我有许多Python生成器,我想将它们组合成一个新的生成器。我可以使用一堆yield语句通过手写的生成器轻松完成此操作。

另一方面,itertools模块是为这样的事情而制作的,对我而言,似乎创建我需要的生成器的pythonic方法是将itertools的各种迭代器插在一起模块。

然而,在手头的问题中,它很快变得相当复杂(发电机需要保持一种状态---例如,是否正在处理第一个或后一个项目 - ,第i个输出还取决于对于第i个输入项的条件和各种输入列表在加入生成的列表之前必须以不同方式处理。

由于解决我的问题的标准迭代器的组成是---由于写下源代码的一维性质 - 几乎不可理解,我想知道使用标准{{1}是否有任何优势生成器与手写的生成器函数(在基本和更高级的情况下)。实际上,我认为在90%的情况下,手写版本更容易阅读 - 可能是因为与链接迭代器的功能风格相比,它们更具势在必行。

修改

为了说明我的问题,这是一个(玩具)示例:让itertoolsa为两个相同长度的迭代(输入数据)。 b的项由整数组成,a的项目本身是可迭代的,其各个项目是字符串。输出应对应于以下生成器函数的输出:

b

如果我使用生成器表达式和函数式写下函数式的相同程序 from itertools import * def generator(a, b): first = True for i, s in izip(a, b): if first: yield "First line" first = False else: yield "Some later line" if i == 0: yield "The parameter vanishes." else: yield "The parameter is:" yield i yield "The strings are:" comma = False for t in s: if comma: yield ',' else: comma = True yield t 模块,我最终得到的结果如下:

itertools

示例

from itertools import *
def generator2(a, b):
    return (z for i, s, c in izip(a, b, count())
            for y in (("First line" if c == 0 else "Some later line",),
                      ("The parameter vanishes.",) if i == 0
                      else ("The parameter is:", i),
                      ("The strings are:",),
                      islice((x for t in s for x in (',', t)), 1, None))
            for z in y)

这可能比我的第一个解决方案更优雅,但它看起来像是一次写一次不能理解的代码。我想知道这种编写我的发电机的方式是否有足够的优势,应该这样做。

PS:我想我的功能解决方案的一部分问题是,为了最大限度地减少Python中的关键字数量,一些关键字如“for”,“if”和“else”已被回收用于表达式中他们在表达式中的位置需要习惯(生成器表达式>>> a = (1, 0, 2), ("ab", "cd", "ef") >>> print([x for x in generator(a, b)]) ['First line', 'The parameter is:', 1, 'The strings are:', 'a', ',', 'b', 'Some later line', 'The parameter vanishes.', 'The strings are:', 'c', ',', 'd', 'Some later line', 'The parameter is:', 2, 'The strings are:', 'e', ',', 'f'] >>> print([x for x in generator2(a, b)]) ['First line', 'The parameter is:', 1, 'The strings are:', 'a', ',', 'b', 'Some later line', 'The parameter vanishes.', 'The strings are:', 'c', ',', 'd', 'Some later line', 'The parameter is:', 2, 'The strings are:', 'e', ',', 'f'] 中的排序看起来,至少在我看来,不如经典z for x in a for y in x for z in y循环中的排序自然:for

1 个答案:

答案 0 :(得分:7)

我做了一些分析,常规生成器功能比第二个生成器或我的实现快。

$ python -mtimeit -s'import gen; a, b = gen.make_test_case()' 'list(gen.generator1(a, b))'
10 loops, best of 3: 169 msec per loop

$ python -mtimeit -s'import gen; a, b = gen.make_test_case()' 'list(gen.generator2(a, b))'
10 loops, best of 3: 489 msec per loop

$ python -mtimeit -s'import gen; a, b = gen.make_test_case()' 'list(gen.generator3(a, b))'
10 loops, best of 3: 385 msec per loop

它也恰好是最具可读性的,所以我想我会选择它。话虽这么说,我仍然会发布我的解决方案,因为我认为这是一个更简洁的例子,你可以用itertools做的函数式编程(尽管显然仍然不是最优的,我觉得它应该能够吸引常规的生成器函数。我会破解它)

def generator3(parameters, strings):
    # replace strings with a generator of generators for the individual charachters
    strings = (it.islice((char for string_char in string_ for char in (',', string_char)), 1, None)
               for string_ in strings)

    # interpolate strings with the notices
    strings = (it.chain(('The strings are:',), string_) for string_ in strings)

    # nest them in tuples so they're ate the same level as the other generators
    separators = it.chain((('First line',),), it.cycle((('Some later line',),)))

    # replace the parameters with the appropriate tuples
    parameters = (('The parameter is:', p) if p else ('The parameter vanishes.',)
                  for p in parameters)

    # combine the separators, parameters and strings
    output = it.izip(separators, parameters, strings)

    # flatten it twice and return it
    output = it.chain.from_iterable(output)
    return it.chain.from_iterable(output)   

供参考,测试用例是:

def make_test_case():
    a = [i % 100 for i in range(10000)]
    b = [('12345'*10)[:(i%50)+1] for i in range(10000)]
    return a, b