让我们简化问题。定义:
def get_petters():
for animal in ['cow', 'dog', 'cat']:
def pet_function():
return "Mary pets the " + animal + "."
yield (animal, pet_function)
然后,就像问题中一样,我们得到:
。
>>> for name, f in list(get_petters()):
... print(name + ":", f())
cow: Mary pets the cat.
dog: Mary pets the cat.
cat: Mary pets the cat.
但是如果我们避免先创建一个
list()
:
>>> for name, f in get_petters():
... print(name + ":", f())
cow: Mary pets the cow.
dog: Mary pets the dog.
cat: Mary pets the cat.
发生了什么?为什么这个微小的差别会完全改变我们的结果?
如果我们查看
list(get_petters())
,从不断变化的内存地址可以清楚地看出,我们确实产生了三个不同的函数:
>>> list(get_petters())
[('cow', <function get_petters.<locals>.pet_function at 0x7ff2b988d790>),
('dog', <function get_petters.<locals>.pet_function at 0x7ff2c18f51f0>),
('cat', <function get_petters.<locals>.pet_function at 0x7ff2c14a9f70>)]
然而,看一下这些函数所绑定的
cell
:
>>> for _, f in list(get_petters()):
... print(f(), f.__closure__)
Mary pets the cat. (<cell at 0x7ff2c112a9d0: str object at 0x7ff2c3f437f0>,)
Mary pets the cat. (<cell at 0x7ff2c112a9d0: str object at 0x7ff2c3f437f0>,)
Mary pets the cat. (<cell at 0x7ff2c112a9d0: str object at 0x7ff2c3f437f0>,)
>>> for _, f in get_petters():
... print(f(), f.__closure__)
Mary pets the cow. (<cell at 0x7ff2b86b5d00: str object at 0x7ff2c1a95670>,)
Mary pets the dog. (<cell at 0x7ff2b86b5d00: str object at 0x7ff2c1a952f0>,)
Mary pets the cat. (<cell at 0x7ff2b86b5d00: str object at 0x7ff2c3f437f0>,)
对于两个循环,
cell
对象在迭代过程中保持不变。然而,预期的是,在第二个循环中它所引用的特定
str
会发生变化。
cell
对象指的是在调用
get_petters()
时创建的
animal
。然而,
animal
会随着生成器函数的运行而改变其所引用的
str
对象。
在第一个循环中,每次迭代期间我们都创建了所有的
f
,但只有在生成器
get_petters()
完全耗尽并已经创建了一个函数列表后才会调用它们。
在第二个循环中,每次迭代期间,我们都会暂停
get_petters()
生成器并在每次暂停后调用
f
。因此,我们最终检索到的是生成器函数暂停时刻的
animal
值。
正如 @Claudiu 在
类似问题 的回答中所说:
创建了三个单独的函数,但它们都具有闭包环境 - 在本例中为全局环境(或者如果循环放置在另一个函数内,则为外部函数的环境)。然而这恰恰是问题所在——在该环境中,
animal
被改变,所有的闭包都引用同一个
animal
。
for animal in ['cat', 'dog', 'cow']
... 我相信总会有人来解释这个问题 - 这是 Python 中的一个陷阱 :) - Jon Clements