让我们简化问题。定义:
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
这些函数绑定到的 s:
>>> 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
已经创建了一个函数之后才调用它们。
在第二个循环中,在每次迭代期间,我们暂停get_petters()
生成器并在每次暂停后调用f
。因此,我们最终检索到animal
生成器函数暂停的那一刻的值。
正如@Claudiu 对类似问题的回答:
创建了三个单独的函数,但它们每个都有定义它们的环境的闭包 - 在这种情况下,全局环境(如果循环放置在另一个函数中,则为外部函数的环境)。然而,这正是问题所在——在这种环境中,animal
是变异的,并且闭包都引用了相同的animal
.
[编者注:i
已更改为animal
。]