解答一个关于生成器的问题

Python 2018-08-15

起步

我回答了sf上关于生成器的问题,觉得很有必要记录下,问题链接:https://segmentfault.com/q/1010000016013187

问题描述

g=(i for i in range(4))
for i in [1,10]:
    g=(i+j for j in g)
print(list(g))

请问为什么结果是:[20, 21, 22, 23] 而不是:[11,12,13,14] 呢?

回答

这个要解释起来可能会比较绕,生成器只会在被需要的时候才会执行代码,但这里还需要另一个知识前提,那就是推导式中的变量是临时变量,不会影响到其他变量的,简单来看个例子:

x = 5
a = [x for x in range(3)]

print(a)    # [0, 1, 2]
print(x)    # 5

不记得是那个python版本处理了这个问题了,在一些比较旧的版本里是没有变量保护机制的,至少我用的是 3.6 版本有。同理,来看下这个和题目中比较接近的:

g=(i for i in range(4))
i = 8
print(list(g))

你猜,这里会打印什么,是 [0, 1, 2, 3] 。生成器中的 i 是受保护的,因此与外部变量 i 无关,它的取值就一定是 0123。

再看一个:

a = 1
g=(a + i for i in range(4))
a = 5
print(list(g))

这里 i 是受保护的,而 a 并没有,由于后续 a 的值是 5,所以打印语句中生成器应该是 <gen 5 + 0, 5 + 1, ...> 的存在。

好,终于能谈谈题目中的代码了。 第一行的 g=(i for i in range(4)) 中,i 是受保护的,所以它的迭代永远都是 0~3

for i in [1,10]:
    g=(i+j for j in g)

这里的 j 也属于受保护的,在第一次循环中,它的值就是 g 初始时的产出 0~3。而这里的 i 不受保护,只是进行变量绑定,在生成器生成数据时才获取其值。

第一次循环后 g 的值:

<gen i+0, i+1, i+2, i+3>

第二次循环, 推导式中j就是生产的值,虽然此时 i=10 ,但i是后续绑定的,所以出生产为 i+0, i+1, i+2,i+3 第二次循环后 g 的值:

<gen i+i+0, i+i+1, i+i+2, i+i+3>

最后打印语句时,i的值是循环体最后一次的值(10),所以打印输出 20, 21, 22, 23

顺便对楼上的,即:

g=(i for i in range(4))
for i in [1,10]:
    g=(i+j for j in g)
for i in g:
    print(i)    #输出会是20,41,84,171

做个解释。

在第二个循环体开始前,g 生成器是 <gen i+i+0, i+i+1,...,i+i+3>

for i in g 表明,从g生成的数据赋值给i。第一个数据i是上一次循环体最后一个值(10)所以生成 20,并赋值给 i;

第二次循环在从生成器中获取了 i+i+1 ,此时 i 是 20(上一次循环给赋值的),所以生成是 41,再次赋值给 i;

第三次循环,取得 i+i+2 此时 i 的值是41, 所以得到的生成值是 83, 再次赋值给 i;

...等


本文由 hongweipeng 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

如果对您有用,您的支持将鼓励我继续创作!