每次我不得不拼写for:yield循环时,都会让我感到不舒服。这真的是在Python中编写递归生成器的方式吗?还是有更好的(更习惯用的、性能更好的等)替代方案?
有一个更好的替代方案:
yield from comb(...)
这实际上与以下操作相同:
for out in comb(...):
yield out
这需要Python 3.3。如果你还在使用Python 2.x(或旧版的3.x),你必须坚持使用旧方法,因为Python 2的语法在2.7之后将永远不会再更新(而3.0到3.2显然也是一样的)。
首先,请查看Wessie在评论中提到的
纯Python yield from。此版本仅适用于单个级别的“yield from”,但底部有一个链接指向更灵活和优化的(但更难理解的)版本。它似乎并没有实际工作(我在
_stack
上得到了一个
NameError
,但看起来应该很容易修复。如果可以,在最外层生成器上放置一个
@supergenerator
装饰器,并且性能可接受,则有您的答案。
如果不行,有各种技巧可以处理多个级别的yield循环,而不是在每个级别上进行处理。但是,它们中没有一个可以将您降至0级,而且真的很少值得做。例如:
一旦您考虑到序列而不是生成器函数,就很明显我们所要做的就是展平一个序列。无论您要展平N级,展平到达非可迭代的级别,展平满足其他可预测条件等,都有一个简单的算法;您只需要选择正确的算法。但是它会使您的代码更符合惯例、可读性更强、性能更好吗?很少。让我们看一个超级简单的情况。
def flatten(seq, levels=1):
for level in range(levels):
seq = itertools.chain.from_iterable(seq)
return seq
所以:
def a():
yield 1
yield 2
yield 3
def b():
yield a()
def c():
yield b()
def d():
yield c()
for i in flatten(d(), 3):
print i
好处是我只需要在一个地方处理嵌套,即调用站点,而不是在每个生成器的3个位置处理。代价是对读者来说不太明显发生了什么,并且更容易出错。(好吧,在这种情况下不太可能...但想象一下展开直到
lambda x: isinstance(list)
,对其进行大量测试,发布它,然后有人在
tuple
上调用
comb
...)治疗比疾病更糟糕,这就是为什么我称之为技巧。
除非展开确实是算法的自然部分,或者某些中间步骤是您不能或不想触摸的代码,或者以这种方式构造事物是有用的说明或提醒,或者...
只是为了好玩,我编写了一个全唱全跳的任意展平函数,并将其提交为Erik Rose的漂亮的
more-itertools库的补丁。即使他不接受它,您也可以在
我的分支中找到它——它被称为
collapse
,并且是文件中的最后一个函数。
yield from <generator>
语法结构,但目前尚未将其移植回早期版本。 - Wessieyield from
语法。它是为这些事情而设计的。 - JBernardo