Python 3.x Python3生成器令人费解的行为
以下两个Python3代码段的不同之处在于我认为没有什么区别。然而,当在MacOSX下的Python3.5中从空闲运行时,它们会产生不同的结果。在运行每个代码片段之前,我重新启动了pythonshell 片段1:Python 3.x Python3生成器令人费解的行为,python-3.x,Python 3.x,以下两个Python3代码段的不同之处在于我认为没有什么区别。然而,当在MacOSX下的Python3.5中从空闲运行时,它们会产生不同的结果。在运行每个代码片段之前,我重新启动了pythonshell 片段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)
## 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似乎有着深远的影响
我错过了什么?我试图将示例显著缩小,但没有成功。关键在于以下两行:
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
变量(a
、a2
和a3
)来避免混淆
a = (k for k in range(2, 10))
将创建一个生成器A
,此时list(A)
将生成[2,3,4,5,6,7,8,9]
p = next(a)
a2 = (k for k in a if k % p != 0)
第一个值从a
中消耗,并分配给变量p
<代码>p变为2
,此时列表(a)
将导致[3,4,5,6,7,8,9]
p = next(a)
a2 = (k for k in a if k % p != 0)
将创建一个新的生成器。它生成由a
生成的与条件k%p!=0
。由于p
当前等于2
,列表(a2)
将导致[3,5,7,9]
(顺便说一句,生成器a
也将在该过程中耗尽)
从生成器a2
请求一个新值。后者从a
请求一个值,获取3
,并检查它是否符合上述条件k%p!=0
。由于p
当前等于2
,它确实是这样,并且a2
产生3
,这成为p
的新值
由于从a
中消耗了一个值,列表(a)
此时将导致[4,5,6,7,8,9]
。但是,列表(a2)
现在将导致[4,5,7,8]
!那是因为k%p!=0
将使用p
的当前值,即3
,使a2
跳过6
和9
的值
a3 = (k for k in a2 if k % p != 0)
新的生成器a3
与a2
基本相同,因为它们具有相同的条件
print(list(a3))
发电机a3
耗尽,而发电机a2
又会耗尽a
中的值,从而产生观察结果[4,5,7,8]
在这里,
a
产生了[4,5,6,7,8,9]
,但是3的倍数已经被a2
过滤掉了,而a3
没有过滤任何其他内容(因为它基本上与a2
相同)。这是前面问题的重复(我承认,不是很容易找到)--生成器表达式在使用时拾取p
的值,而不是在定义时。这里应该有“后期绑定生成器表达式”之类的东西。一个较短的例子是p=2;a=(k表示范围(2,10)内的k,如果k%p==0);p=3;打印(列表(a))
它打印3、6、9
。p
的值在生成器执行之前(转换为列表时)不会使用。啊,我明白了。谢谢,谢谢,你的解释很清楚。是否有一种简单的方法可以将生成器中的值“冻结”为定义生成器时的值,而不是使用生成器时的值?或者这是个坏主意?您需要确保的唯一一件事是生成器中使用的变量不会被修改(p
)。您发布的第一个代码片段已经完全避免了这个问题,因为它使用了不同的变量,即q
。另一个选项是创建一个函数,使用它自己的本地p
变量为您创建一个生成器:def make_-gen(gen,p):返回(k表示gen中的k,如果k%p!=0)
,然后p=2;a2=制造发电机(a,p)
。p
的后续修改不会影响a2
,因为后者将使用本地p
变量。