Vamos simplificar a pergunta. Definir:
def get_petters():
for animal in ['cow', 'dog', 'cat']:
def pet_function():
return "Mary pets the " + animal + "."
yield (animal, pet_function)
Então, assim como na pergunta, temos:
>>> 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.
Mas se evitarmos criar um list()
primeiro:
>>> for name, f in get_petters():
... print(name + ":", f())
cow: Mary pets the cow.
dog: Mary pets the dog.
cat: Mary pets the cat.
O que está acontecendo? Por que essa diferença sutil muda completamente nossos resultados?
Se olharmos list(get_petters())
, fica claro pela mudança de endereços de memória que realmente produzimos três funções diferentes:
>>> 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>)]
No entanto, dê uma olhada nos cell
s aos quais essas funções estão vinculadas:
>>> 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>,)
Para ambos os loops, o cell
objeto permanece o mesmo ao longo das iterações. No entanto, como esperado, a str
referência específica varia no segundo loop. O cell
objeto se refere a animal
, que é criado quando get_petters()
é chamado. No entanto, animal
muda a qual str
objeto ele se refere quando a função do gerador é executada .
No primeiro loop, durante cada iteração, criamos todos os f
s, mas só os chamamos depois que o gerador get_petters()
está completamente esgotado e umlist
de funções já tiver sido criado.
No segundo loop, durante cada iteração, estamos pausando o get_petters()
gerador e chamando f
após cada pausa. Assim, acabamos recuperando o valor deanimal
naquele momento no tempo em que a função do gerador está pausada.
Como @Claudiu responde a uma pergunta semelhante :
Três funções separadas são criadas, mas cada uma tem o fechamento do ambiente em que estão definidas - neste caso, o ambiente global (ou o ambiente da função externa se o loop for colocado dentro de outra função). Este é exatamente o problema, entretanto - neste ambiente, animal
é mutado, e todos os encerramentos referem-se ao mesmo animal
.
[Nota do editor: i
foi alterado para animal
.]
for animal in ['cat', 'dog', 'cow']
... Tenho certeza de que alguém vai aparecer e explicar isso - é um daqueles pegadinhas do Python :)