Simplifions la question. Définir:
def get_petters():
for animal in ['cow', 'dog', 'cat']:
def pet_function():
return "Mary pets the " + animal + "."
yield (animal, pet_function)
Ensuite, comme dans la question, nous obtenons:
>>> 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.
Mais si on évite de créer une list()
première:
>>> 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 se passe-t-il? Pourquoi cette différence subtile change-t-elle complètement nos résultats?
Si nous regardons list(get_petters())
, il est clair d'après les adresses mémoire changeantes que nous fournissons en effet trois fonctions différentes:
>>> 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>)]
Cependant, regardez les cell
s auxquels ces fonctions sont liées:
>>> 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>,)
Pour les deux boucles, l' cell
objet reste le même tout au long des itérations. Cependant, comme prévu, le spécifique auquel str
il fait référence varie dans la deuxième boucle. L' cell
objet fait référence à animal
, qui est créé lors de l' get_petters()
appel. Cependant, animal
change l' str
objet auquel il fait référence lorsque la fonction de générateur s'exécute .
Dans la première boucle, à chaque itération, nous créons tous les f
s, mais nous ne les appelons que lorsque le générateur get_petters()
est complètement épuisé et qu'une list
de fonctions est déjà créée.
Dans la deuxième boucle, à chaque itération, nous mettons le get_petters()
générateur en pause et appelons f
après chaque pause. Ainsi, nous finissons par récupérer la valeur de animal
à ce moment dans le temps où la fonction de générateur est mise en pause.
Comme @Claudiu répond à une question similaire :
Trois fonctions distinctes sont créées, mais elles ont chacune la fermeture de l'environnement dans lequel elles sont définies - dans ce cas, l'environnement global (ou l'environnement de la fonction externe si la boucle est placée à l'intérieur d'une autre fonction). C'est exactement le problème, cependant - dans cet environnement, il animal
est muté, et les fermetures se réfèrent toutes au même animal
.
[Note de l'éditeur: i
a été remplacé par animal
.]
for animal in ['cat', 'dog', 'cow']
... Je suis sûr que quelqu'un viendra et vous expliquera cela - c'est un de ces pièges à Python :)