我有许多 Python 生成器,我想将它们组合成一个新的生成器。我可以通过使用一堆 yield
语句的手写生成器轻松地做到这一点。
另一方面,itertools
模块就是为这样的事情而制作的,对我来说,创建我需要的生成器的 pythonic 方法似乎是将 的各种迭代器连接在一起>itertools
模块。
然而,在手头的问题中,它很快就会变得相当复杂(生成器需要保持某种状态---例如是否正在处理第一个或后面的项目---,第i个输出进一步取决于在第 i 个输入项和各种输入列表的条件下,在将它们连接到生成的列表之前必须进行不同的处理。
因为可以解决我的问题的标准迭代器的组合是---由于写下源代码的一维性质---几乎不可理解,我想知道使用标准 itertools 是否有任何优势
生成器与手写生成器函数(在基本和更高级的情况下)。实际上,我认为在 90% 的情况下,手写版本更易于阅读 --- 可能是因为与链接迭代器的函数式风格相比,它们更具命令式风格。
编辑
为了说明我的问题,这里有一个(玩具)示例:设 a
和 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)
示例
>>> 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']
这可能比我的第一个解决方案更优雅,但它看起来像是一段只写一次,以后不理解的代码。我想知道这种编写生成器的方式是否具有足够的优势,应该这样做。
P.S.:我想我的函数式解决方案的部分问题是为了尽量减少 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
).
最佳答案
我做了一些分析,常规生成器函数比您的第二个生成器或我的实现要快得多。
$ 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
关于python - itertools 或手写生成器 - 哪个更好?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3849702/