Python 3生成器的令人费解的行为

时间:2016-01-23 23:13:08

标签: python-3.x

以下两个Python 3代码片段的不同之处在于我认为没有任何区别的更改。但是,当在Mac OS X下的Python 3.5中从IDLE运行时,它们会产生不同的结果。我在运行每个代码段之前重新启动了Python shell。

摘录1:

## The following prints [5, 7] as expected
a = (k for k in range(2, 10))    # generator for [2, 3, 4, 5, 6, 7, 8, 9]
p = next(a)                      # p is 2
a = (k for k in a if k % p != 0) # remove multiples of 2: generator for [3, 5, 7, 9]
q = next(a)                      # q is 3
a = (k for k in a if k % q != 0) # remove multiples of 3: generator for [5, 7]
print(list(a))                   # prints [5, 7]

摘录2:[唯一的区别是在第四行和第五行代码中使用 p 而不是 q 。]

## The following prints [4, 5, 7, 8]
a = (k for k in range(2, 10))
p = next(a)
a = (k for k in a if k % p != 0)
p = next(a)                      # Using p instead of q
a = (k for k in a if k % p != 0) # Ditto
print(list(a))                   # prints [4, 5, 7, 8]

因此,重用对象名称​​ p 似乎具有深远的影响。

我错过了什么?我没有成功地试图让这个例子变得更小。

1 个答案:

答案 0 :(得分:0)

问题在于以下两行:

a = (k for k in a if k % p != 0)
p = next(a)                      # Using p instead of q

“新a”会产生符合条件a的“旧k % p != 0”元素。但是,该条件仅在生成器a请求新元素时进行评估,然后使用值p。在你的情况下,那将是 3 (并且不再 2,正如人们所期望的那样)。

更长的解释:

让我们跟踪代码中发生的事情。我使用了三个不同的a变量(aa2a3)来避免混淆。

a = (k for k in range(2, 10))

创建了一个生成器a,此时list(a)将导致[2, 3, 4, 5, 6, 7, 8, 9]

p = next(a)

第一个值来自a,并已分配给变量pp变为2list(a)此时会产生[3,4,5,6,7,8,9]。

a2 = (k for k in a if k % p != 0)

创建一个新的生成器。它会生成a所产生的与条件k % p != 0匹配的所有值。由于p当前等于2list(a2)会导致[3, 5, 7, 9](BTW,生成器a也会在此过程中耗尽。)

p = next(a2)

从生成器a2请求新值。后者从a请求值,获取3,并检查它是否与上述条件k % p != 0匹配。由于p目前等于2,因此确实如此,而a2会产生3,这将成为p的新值。

由于a消耗了一个值,list(a)此时会产生[4, 5, 6, 7, 8, 9]。但是,list(a2)现在会产生[4, 5, 7, 8]!这是因为k % p != 0将使用p的当前值3,使a2跳过值69。< / p>

a3 = (k for k in a2 if k % p != 0)

新生成器a3a2基本相同,因为它们都具有相同的条件。

print(list(a3))

生成器a3已耗尽,从而耗尽a2,这会耗尽a的值,从而导致观察到的结果为[4, 5, 7, 8]

此处,a已产生[4, 5, 6, 7, 8, 9],但3的倍数已被a2过滤掉,a3未过滤任何其他内容(因为它是与a2)基本相同。