itertools还是手写生成器,哪个更好?

7 投票
1 回答
1191 浏览
提问于 2025-04-16 04:55

我有几个Python生成器,想把它们合并成一个新的生成器。我可以通过手动编写一个生成器,使用一堆yield语句来轻松实现这个目标。

另一方面,itertools模块就是为这种事情设计的,感觉用这个模块里的各种迭代器来创建我需要的生成器是更符合Python风格的做法。

不过,眼下这个问题变得相当复杂(这个生成器需要保持某种状态——比如正在处理的是第一个项目还是后面的项目——第i个输出还依赖于第i个输入项目的条件,而各种输入列表在合并到生成的列表之前需要以不同的方式处理)。

由于标准迭代器的组合几乎让人难以理解(因为写代码是线性的),我在想使用标准的itertools生成器和手动编写的生成器函数相比,是否有什么好处(无论是基础情况还是更复杂的情况)。实际上,我觉得在90%的情况下,手动编写的版本更容易阅读——可能是因为它们的写法更直接,而不是像链式迭代器那样的函数式风格。

编辑

为了说明我的问题,这里有一个(简单的)例子:设ab是两个相同长度的可迭代对象(输入数据)。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)

示例

>>> 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']

这可能比我最初的解决方案更优雅,但看起来像是一段写一次后就不理解的代码。我在想,这种写生成器的方式是否有足够的优势值得去这样做。

附言:我觉得我在函数式解决方案上的问题部分是因为为了减少Python中的关键字数量,一些关键字像“for”、“if”和“else”被重新利用在表达式中,这样它们在表达式中的位置就需要适应(生成器表达式z for x in a for y in x for z in y的顺序,至少对我来说,看起来不如经典的for循环顺序自然:for x in a: for y in x: for z in y: yield z)。

1 个回答

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

撰写回答