Python itertools或手写生成器-最好是什么?

Python itertools或手写生成器-最好是什么?,python,iterator,generator,Python,Iterator,Generator,我有很多Python生成器,我想将它们组合成一个新的生成器。我可以通过一个手工编写的生成器,使用一堆yield语句轻松地完成这项工作 另一方面,itertools模块是为类似的事情而设计的,在我看来,创建我需要的生成器的pythonic方法似乎是将该itertools模块的各种迭代器插在一起 然而,手头的问题很快就变得相当复杂了(生成器需要保持一种状态,例如,第一个项目或后面的项目是否正在处理,第i个输出进一步取决于第i个输入项目的条件,并且在将各种输入列表加入到生成的列表之前,必须对其进行不同

我有很多Python生成器,我想将它们组合成一个新的生成器。我可以通过一个手工编写的生成器,使用一堆
yield
语句轻松地完成这项工作

另一方面,
itertools
模块是为类似的事情而设计的,在我看来,创建我需要的生成器的pythonic方法似乎是将该
itertools
模块的各种迭代器插在一起

然而,手头的问题很快就变得相当复杂了(生成器需要保持一种状态,例如,第一个项目或后面的项目是否正在处理,第i个输出进一步取决于第i个输入项目的条件,并且在将各种输入列表加入到生成的列表之前,必须对其进行不同的处理

由于编写源代码的一维特性,解决我问题的标准迭代器的组成几乎无法理解,因此我想知道使用标准
itertools
生成器与手工编写的生成器函数(在基本和更高级的情况下)相比是否有任何优势事实上,我认为在90%的情况下,手写版本更容易阅读——可能是因为与链式迭代器的函数式风格相比,手写版本更具命令式风格

编辑

为了说明我的问题,这里有一个(玩具)示例:让
a
b
是两个长度相同的ITerable(输入数据)。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
module,我最终得到了如下结果:

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:for z in y:yield z
).

我做了一些分析,常规生成器函数比第二个生成器或我的实现快得多

$ 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 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

@Marc,你仍然没有解决真正的混淆问题,即你是否想要“参数是:”和
i
单独或作为元组生成。更正了第一段代码(很抱歉混淆,但我匆忙键入了第一个生成器函数,这使我在编写时而不是for)之间的区别“收益率x;收益率y”和“(x,y)”:输出将被另一个for循环消耗,因此就我的目的而言,输出类似于“(x,y)”或“iter((x,y))没有区别“。但是在我上面的示例中,函数generator2输出一个生成器,其行为与函数生成器的输出完全相同。我没有看到generator2输出元组(我已经在我的系统上运行了代码。)@马克。你说得对。我的错,第二个很难理解,但现在我看清楚了。非常感谢你的版本生成器3(它向我展示了一个人可以使用大量迭代器编写可读性很强的代码)还有你的评测。我已经在我的计算机上重复了测试,并得到了相同的计时比率。目前,我将坚持使用生成器函数,这是最容易理解的。然而,当一个人也可以编写生成器函数时,何时使用迭代器仍然是一个悬而未决的问题。(或者应该把迭代器看作是Python发行版的操作符模块中的方便函数吗?)P.S.:我刚刚发现我必须坚持使用生成器函数。它可以做一件事,我不能处理生成器表达式,即捕获异常。
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