我在列表理解中使用了生成器,并且其中一个生成器提前结束时出现了一些意外行为。为什么在列表理解之外创建生成器会导致行为发生变化?
我创建的生成器如下:
def inc_range(a,b):
for i in range(min(a,b), max(a,b) + 1):
yield i
第一种呼叫方式如下:
[(i,j) for i in inc_range(1,3) for j in inc_range(4,6)]
这给了我以下结果:
[(1, 4), (1, 5), (1, 6), (2, 4), (2, 5), (2, 6), (3, 4), (3, 5), (3, 6)]
第二种调用方式如下:
a = inc_range(1,3)
b = inc_range(4,6)
[(i,j) for i in a for j in b]
这给了我以下内容:
[(1, 4), (1, 5), (1, 6)]
实验,下面的两个例子给了我第一个结果:
a = range(1,4)
b = range(4,7)
[(i,j) for i in a for j in b]
a = (i for i in range(1,4))
b = (i for i in range(4,7))
a = list(a)
b = list(b)
[(i,j) for i in a for j in b]
以下内容再次给了我第二个结果。
a = (i for i in range(1,4))
b = (i for i in range(4,7))
[(i,j) for i in a for j in b]
我在这里针对发电机违反了什么规则?为什么在列表理解中使用生成器之前将生成器分配给变量与直接使用生成器相比有什么区别?
答案
查看以下有助于我了解此处发生情况的答案:
答案 0 :(得分:6)
要获得理想的结果,“内部”生成器必须运行与“外部”生成器产生一个值一样多的次数。
但是,第一次运行后,“内部”生成器已耗尽,无法再次运行。
添加print
可以说明这一点(简化示例):
>>> def inc(a, b):
... for i in range(a, b):
... print(i)
... yield i
...
>>> a = inc(1, 4)
>>> b = inc(4, 7)
>>> [(i, j) for i in a for j in b]
1 # <-- a begins to run
4 # <-- b begins to run
5
6 # <-- b exhausted here
2 # <-- a continued, but not resulting in list item, because lacking value from b
3
[(1, 4), (1, 5), (1, 6)]
之所以不将生成器存储在变量中的原因是按预期的,是因为为“外部”生成器的每次迭代创建了一个新的“内部”生成器。再次通过一些印刷品进行说明:
>>> def inc(a, b):
... print('started', a, b)
... for i in range(a, b):
... yield i
...
>>> [(i, j) for i in inc(1, 4) for j in inc(4, 7)]
started 1 4
started 4 7
started 4 7
started 4 7
[(1, 4), (1, 5), (1, 6), (2, 4), (2, 5), (2, 6), (3, 4), (3, 5), (3, 6)]
使用range
对象或列表可以按预期工作的原因是因为它们可以任意多次迭代而不会被耗尽。
答案 1 :(得分:2)
生成器是一个可迭代的对象,因此当您在列表之外调用它时,它仅返回下一项。
a = inc_range(1,3)
b = inc_range(4,6)
c = inc_range(7,9)
[(i,j,k) for i in a for j in b for k in c]
这只会在运行时产生c中k的元素
因此,在将其定义为数组时,需要像遍历所有对象一样进行迭代。
[(i,j,k) for i in inc_range(1,3) for j in inc_range(3,6) for k in inc_range(7,9)]
这迫使生成器在每次迭代中产生所有值。