web-dev-qa-db-fra.com

Variables locales dans les fonctions imbriquées

D'accord, restez avec moi à ce sujet, je sais que ça va avoir l'air horriblement compliqué, mais 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, fondamentalement, pourquoi n'ai-je pas trois animaux différents? Le cage n'est-il pas "empaqueté" 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 se heurter à ce genre de problèmes signifie généralement que l'on "fait mal", mais j'aimerais comprendre ce qui se passe.

101
noio

La fonction imbriquée recherche les variables de la portée parent lorsqu'elle est exécutée, et non 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 ensuite référencée via une cellule de fermeture, index 0. Le la fermeture elle-même pointe vers la variable locale cage dans la fonction get_petters.

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, la fonction get_petters A déjà terminé de calculer ses résultats. La variable locale cage à un moment donné au cours de l'exécution a été affectée à chacune des chaînes 'cow', 'dog' Et 'cat', 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 étendue de fonction , ou liez la variable comme valeur par défaut pour un paramètre de mot clé .

  • Exemple de fonction partielle, utilisant functools.partial() :

    from functools import partial
    
    def pet_function(cage=None):
        print "Mary pets the " + cage.animal + "."
    
    yield (animal, partial(gotimes, partial(pet_function, cage=cage)))
    
  • Création d'un nouvel exemple de portée:

    def scoped_cage(cage=None):
        def pet_function():
            print "Mary pets the " + cage.animal + "."
        return pet_function
    
    yield (animal, partial(gotimes, scoped_cage(cage)))
    
  • Liaison de la variable comme valeur par défaut pour un paramètre de mot clé:

    def pet_function(cage=cage):
        print "Mary pets the " + cage.animal + "."
    
    yield (animal, partial(gotimes, pet_function))
    

Il n'est pas nécessaire de définir la fonction scoped_cage Dans la boucle, la compilation n'a lieu qu'une seule fois, pas à chaque itération de la boucle.

109
Martijn Pieters

Ma compréhension est que la cage est recherchée dans l'espace de noms de la fonction parent lorsque la fonction pet_function obtenue est réellement appelée, pas avant.

Alors quand tu fais

funs = list(get_petters())

Vous générez 3 fonctions qui trouveront la dernière cage créée.

Si vous remplacez votre dernière boucle par:

for name, f in get_petters():
    print name + ":", 
    f()

Vous obtiendrez en fait:

cow: Mary pets the cow.
dog: Mary pets the dog.
cat: Mary pets the cat.
12
Nicolas Barbey

Cela découle des éléments suivants

for i in range(2): 
    pass

print i is 1

après avoir itéré la valeur de i est paresseusement stockée comme sa 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 liste, elle s'exécute sur le générateur, d'où tous les appels à cage (cage.animal) retourner les chats.

6
Andy Hayden