嵌套的生成器表达式意外地表现

时间:2017-01-10 05:00:13

标签: python generator

使用以下代码:

A = [1, 2]
B = [-2, -1]
C = [-1, 2]
D = [0, 2]

ab = (a + b for a in A for b in B)
cd = (c + d for c in C for d in D)
abcd = (e_ab + e_cd for e_ab in ab for e_cd in cd)

len(abcd)预计为16,但实际上是4。如果我使用列表理解,问题就会消失。那是为什么?

3 个答案:

答案 0 :(得分:27)

你只能乘坐发电机列车一次,到达目的地后,不再乘坐。在您的情况下,cd生成器已用尽,然后无法再次迭代。

另一方面,

list个对象在每次上为它们调用iter时创建一个单独的迭代器对象for循环隐式为你):

print(iter([1, 2, 3]))
# <list_iterator at 0x7f18495d4c88> 

并生成一个可以使用的新鲜迭代器。任何时候在其上调用 iter时都会发生这种情况;由于每次都会生成一个新对象,因此您可以多次浏览列表。多次骑行!

简而言之,如果cd更改为列表(通常,将多次迭代的对象):

ab = (a + b for a in A for b in B)
cd = [c + d for c in C for d in D]  # list-comp instead

它将通过cdab中的每个元素创建新的迭代器对象来产生想要的结果:

abcd = (e_ab + e_cd for e_ab in ab for e_cd in cd)
print(len(list(abcd)))
# 16

当然,您也可以使用product中的itertools来实现此目的,但这超出了发生这种情况的原因。

答案 1 :(得分:16)

我认为这是因为你只能迭代生成器一次。因此,在第一次循环e_cd之后,这将不会在外部循环的另一次迭代中产生任何内容。

答案 2 :(得分:12)

当生成器没有其他值返回时,它会引发StopIteration异常。这就是他们发出信号的信号。由于没有内置的方法来重置生成器,当您从生成器创建多级生成器时,它将在第一次遇到StopIteration时停止,而不是像子列表那样导致子生成器循环像对象。

itertools.product()可以产生所需的结果(repl.it here):

import itertools

A = [1, 2]
B = [-2, -1]
C = [-1, 2]
D = [0, 2]

ab = (a + b for a in A for b in B)
cd = (c + d for c in C for d in D)
abcd = (e_ab + e_cd for e_ab, e_cd in itertools.product(ab,cd))