Python中带生成器的嵌套循环在某些情况下无法正常工作?

10

能否有人解释一下使用生成器的嵌套循环的行为?以下是一个例子。

a = (x for x in range(3))
b = (x for x in range(2))
for i in a:
    for j in b:
        print (i,j)

由于某种原因,在第一次迭代后,外部循环不再被评估。结果是:

(0, 0)
(0, 1)

另一方面,如果将生成器直接插入循环中,它会按照我的预期执行。

for i in (x for x in range(3)):
    for j in (x for x in range(2)):
        print (i,j)

给出所有的3x2对。

(0, 0)
(0, 1)
(1, 0)
(1, 1)
(2, 0)
(2, 1)
3个回答

25

这是因为在外层 for 循环的第一次迭代中,b 生成器已经耗尽,导致内部循环为空(例如 for x in ()),因此内部代码不会被执行。这会给人一个错误的印象,好像是外层循环出现了问题。

您的第二个示例之所以有效,是因为每次外层循环都会创建一个新的内部生成器。要修复第一个示例,您必须做相同的事情:

a = (x for x in range(3))
for i in a:
    b = (x for x in range(2))
    for j in b:
        print (i,j)

啊哈!我没有注意到生成器的耗尽。非常感谢你。 - phantomile

8

@lazyr已经非常出色地回答了这个问题,但是值得一提的是,在使用嵌套生成器时,了解itertools.product是很有价值的...

for i, j in itertools.product(range(3), range(2)):
    print (i, j)

或者(如果你有很多值):
for vals in itertools.product(range(45), range(12), range(3)):
    print (sum(vals))

在我看来,这篇文章易读且避免了过多缩进。


0

itertools.product是适用于此示例的最佳选择。但是,在迭代过程中,您可能需要更多选项。以下是一种方法,可以在不使用product方法的情况下仍然获取您示例中的产品:

a = (range(2) for x in range(3))
for i in a:
    for j in i:
        print (i,j)

此外,我使用 pytoolz 函数辅助库中的 itertoolz.concat 来简化/展开这种情况。concat 就像 itertools.chain 一样,但它接受一个单一参数,该参数产生需要被展开的迭代器:
from pytoolz import itertoolz
a = (((x,y) for y in range(2)) for x in range(3))
for i,j in itertoolz.concat(a):
    print (i,j)

所以,上面的代码看起来比 product 方法不太易读,但允许在每个循环级别进行更细粒度的转换/过滤。当然,在最后一次迭代逻辑中,您不需要嵌套 for 循环,这可能很好。

此外,如果您使用 pytoolz 库,您应该使用 cytoolz,它是相同的库编译成 C 语言。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接