让我们简化问题。定义:
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对象引用animal,在get_petters()调用时创建。但是,在生成器函数运行时animal更改str它所指的对象。
在第一个循环中,在每次迭代期间,我们都创建了所有fs,但是只有在生成器get_petters()完全用尽并且list已经创建a 函数之后,才调用它们。
在第二个循环中,在每次迭代期间,我们暂停get_petters()生成器并f在每次暂停后调用。因此,我们最终animal在生成器功能暂停的那一刻检索了值。
正如@Claudiu对类似问题的回答:
创建了三个单独的函数,但是每个函数都封闭了定义它们的环境-在这种情况下,是全局环境(如果将循环放在另一个函数内部,则为外部函数的环境)。不过,这确实是问题所在-在这种环境中,animal变量是突变的,并且所有的闭包都引用相同的animal。
[编者注:i已更改为animal。]
for animal in ['cat', 'dog', 'cow']...我确定有人会来解释这个-它是那些Python陷阱之一:)