D'accord, supportez-moi là-dessus, je sais que ça va paraître horriblement alambiqué, mais s'il vous plaît, aidez-moi à comprendre ce qui se passe.
from functools import partial
class Cage(object):
def __init__(self, animal):
self.animal = animal
def gotimes(do_the_petting):
do_the_petting()
def get_petters():
for animal in ['cow', 'dog', 'cat']:
cage = Cage(animal)
def pet_function():
print "Mary pets the " + cage.animal + "."
yield (animal, partial(gotimes, pet_function))
funs = list(get_petters())
for name, f in funs:
print name + ":",
f()
Donne:
cow: Mary pets the cat.
dog: Mary pets the cat.
cat: Mary pets the cat.
Donc, en gros, pourquoi est-ce que je n'ai pas trois animaux différents? Le cage
«packagé» n'est-il pas dans la portée locale de la fonction imbriquée? Sinon, comment un appel à la fonction imbriquée recherche-t-il les variables locales?
Je sais que rencontrer ce genre de problèmes signifie généralement que l'on «fait mal», mais j'aimerais comprendre ce qui se passe.
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 :)Réponses:
La fonction imbriquée recherche les variables de la portée parent lorsqu'elle est exécutée, pas lorsqu'elle est définie.
Le corps de la fonction est compilé et les variables «libres» (non définies dans la fonction elle-même par affectation) sont vérifiées, puis liées en tant que cellules de fermeture à la fonction, le code utilisant un index pour référencer chaque cellule.
pet_function
a donc une variable libre (cage
) qui est alors référencée via une cellule de fermeture, index 0. La fermeture elle-même pointe vers la variable localecage
dans laget_petters
fonction.Lorsque vous appelez réellement la fonction, cette fermeture est ensuite utilisée pour examiner la valeur de
cage
dans la portée environnante au moment où vous appelez la fonction . C'est là que réside le problème. Au moment où vous appelez vos fonctions, laget_petters
fonction a déjà terminé de calculer ses résultats. Lacage
variable locale à un moment donné au cours de cette exécution a été attribué chacun des'cow'
,'dog'
et des'cat'
cordes, mais à la fin de la fonction,cage
contient cette dernière valeur'cat'
. Ainsi, lorsque vous appelez chacune des fonctions renvoyées dynamiquement, vous obtenez la valeur'cat'
imprimée.La solution consiste à ne pas compter sur les fermetures. Vous pouvez utiliser une fonction partielle à la place, créer une nouvelle portée de fonction ou lier la variable en tant que valeur par défaut pour un paramètre de mot-clé .
Exemple de fonction partielle, utilisant
functools.partial()
:Création d'un nouvel exemple de portée:
Lier la variable en tant que valeur par défaut pour un paramètre de mot-clé:
Il n'est pas nécessaire de définir la
scoped_cage
fonction dans la boucle, la compilation n'a lieu qu'une seule fois, pas à chaque itération de la boucle.la source
Je crois comprendre que la cage est recherchée dans l'espace de noms de la fonction parent lorsque la fonction pet_function produite est effectivement appelée, pas avant.
Alors quand tu fais
Vous générez 3 fonctions qui trouveront la dernière cage créée.
Si vous remplacez votre dernière boucle par:
Vous obtiendrez en fait:
la source
Cela découle de ce qui suit
après l'itération, la valeur de
i
est stockée paresseusement comme valeur finale.En tant que générateur, la fonction fonctionnerait (c'est-à-dire imprimer chaque valeur à son tour), mais lors de la transformation en une liste, elle s'exécute sur le générateur , donc tous les appels à
cage
(cage.animal
) retournent des chats.la source
Simplifions la question. Définir:
Ensuite, comme dans la question, nous obtenons:
Mais si on évite de créer une
list()
première: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:Cependant, regardez les
cell
s auxquels ces fonctions sont liées:Pour les deux boucles, l'
cell
objet reste le même tout au long des itérations. Cependant, comme prévu, le spécifique auquelstr
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érateurget_petters()
est complètement épuisé et qu'unelist
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 appelonsf
après chaque pause. Ainsi, nous finissons par récupérer la valeur deanimal
à ce moment dans le temps où la fonction de générateur est mise en pause.Comme @Claudiu répond à une question similaire :
la source