Simplifiquemos la pregunta. Definir:
def get_petters():
for animal in ['cow', 'dog', 'cat']:
def pet_function():
return "Mary pets the " + animal + "."
yield (animal, pet_function)
Entonces, al igual que en la pregunta, obtenemos:
>>> 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.
Pero si evitamos crear una list()
primera:
>>> for name, f in get_petters():
... print(name + ":", f())
cow: Mary pets the cow.
dog: Mary pets the dog.
cat: Mary pets the cat.
¿Que esta pasando? ¿Por qué esta sutil diferencia cambia completamente nuestros resultados?
Si miramos list(get_petters())
, está claro por las direcciones de memoria cambiantes que de hecho producimos tres funciones 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>)]
Sin embargo, eche un vistazo a los cell
s a los que están vinculadas estas funciones:
>>> 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 bucles, el cell
objeto permanece igual a lo largo de las iteraciones. Sin embargo, como era de esperar, el específico al str
que hace referencia varía en el segundo ciclo. El cell
objeto al animal
que se refiere , que se crea cuando get_petters()
se llama. Sin embargo, animal
cambia el str
objeto al que se refiere mientras se ejecuta la función del generador .
En el primer ciclo, durante cada iteración, creamos todos los f
s, pero solo los llamamos después de que el generador get_petters()
esté completamente agotado y unlist
ya se ha creado función.
En el segundo ciclo, durante cada iteración, estamos pausando el get_petters()
generador y llamando f
después de cada pausa. Así, terminamos recuperando el valor deanimal
en ese momento en el que la función del generador está en pausa.
Como @Claudiu responde a una pregunta similar :
Se crean tres funciones separadas, pero cada una tiene el cierre del entorno en el que están definidas, en este caso, el entorno global (o el entorno de la función externa si el bucle se coloca dentro de otra función). Sin embargo, este es exactamente el problema: en este entorno, animal
está mutado y todos los cierres se refieren al mismoanimal
.
[Nota del editor: i
se ha cambiado a animal
.]
for animal in ['cat', 'dog', 'cow']
... Estoy seguro de que alguien vendrá y explicará esto, es uno de esos problemas de Python :)