Semplifichiamo la domanda. Definire:
def get_petters():
for animal in ['cow', 'dog', 'cat']:
def pet_function():
return "Mary pets the " + animal + "."
yield (animal, pet_function)
Quindi, proprio come nella domanda, otteniamo:
>>> 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.
Ma se evitiamo di creare un list()
primo:
>>> for name, f in get_petters():
... print(name + ":", f())
cow: Mary pets the cow.
dog: Mary pets the dog.
cat: Mary pets the cat.
Cosa sta succedendo? Perché questa sottile differenza cambia completamente i nostri risultati?
Se guardiamo list(get_petters())
, è chiaro dal cambiamento degli indirizzi di memoria che effettivamente produciamo tre diverse funzioni:
>>> 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>)]
Tuttavia, dai un'occhiata ai messaggi a cell
cui sono associate queste funzioni:
>>> 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>,)
Per entrambi i cicli, l' cell
oggetto rimane lo stesso durante le iterazioni. Tuttavia, come previsto, la specifica a cui str
fa riferimento varia nel secondo ciclo. L' cell
oggetto si riferisce a animal
, che viene creato quando get_petters()
viene chiamato. Tuttavia, animal
cambia l' str
oggetto a cui si riferisce durante l'esecuzione della funzione generatore .
Nel primo ciclo, durante ogni iterazione, creiamo tutte le f
s, ma le chiamiamo solo dopo che il generatore get_petters()
è completamente esaurito e una list
delle funzioni è già stata creata.
Nel secondo ciclo, durante ogni iterazione, mettiamo in pausa il get_petters()
generatore e chiamiamo f
dopo ogni pausa. Quindi, finiamo per recuperare il valore di animal
in quel momento nel tempo in cui la funzione del generatore è in pausa.
Come @Claudiu risponde a una domanda simile :
Vengono create tre funzioni separate, ma ciascuna ha la chiusura dell'ambiente in cui sono definite: in questo caso, l'ambiente globale (o l'ambiente della funzione esterna se il ciclo è posto all'interno di un'altra funzione). Questo è esattamente il problema, però: in questo ambiente, animal
è mutato e le chiusure si riferiscono tutte allo stesso animal
.
[Nota dell'editore: i
è stata modificata in animal
.]
for animal in ['cat', 'dog', 'cow']
... sono sicuro che qualcuno verrà e spiegherà questo però - è uno di quei gotcha di Python :)