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