110 votes

Les variables locales en Python fonctions imbriquées

Ok, garder avec moi sur ce, je sais que ça va avoir l'air horriblement compliqué, mais s'il vous plaît m'aider à 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 je n'obtiens pas trois animaux différents? N'est-ce pas l' cage 'emballé' dans le champ d'application de la fonction imbriquée? Si non, comment fonctionne un appel à la fonction imbriquée rechercher les variables locales?

Je sais que de courir dans ce genre de problèmes est généralement signifie que l'on est "faire le mal", mais je voudrais comprendre ce qui se passe.

119voto

Martijn Pieters Points 271458

La fonction imbriquée recherche les variables de la portée parent lorsqu'il est exécuté, pas quand définis.

Le corps de la fonction est compilé, et le "libre" des variables (non défini dans la fonction elle-même par la cession), sont vérifiées, alors forcément que de la fermeture des cellules à la fonction, avec le code à l'aide d'un indice de référence de chaque cellule. pet_function a donc une variable indépendante (cage) qui est référencée par l'intermédiaire d'une fermeture de la cellule, l'index 0. La fermeture elle-même pointe vers la variable locale cage dans la get_petters fonction.

Lorsque vous appelez la fonction, que la fermeture est ensuite utilisé pour regarder la valeur de cage dans les environs du champ d'application au moment de l'appel de la fonction. C'est là que réside le problème. Par le temps que vous appelez de vos fonctions, l' get_petters fonction est déjà fait le calcul des résultats. L' cage variable locale à un certain moment au cours de cette exécution a été assigné à chacun de l' 'cow', 'dog', et 'cat' chaînes, mais à la fin de la fonction, cage contient la dernière valeur 'cat'. Ainsi, lorsque vous appelez chaque renvoyé dynamiquement les fonctions, vous obtenez la valeur 'cat' imprimé.

Le travail est de ne pas s'appuyer sur les fermetures. Vous pouvez utiliser une fonction partielle au lieu de cela, créer un nouveau domaine de la fonction, ou de lier la variable comme valeur par défaut pour un mot-clé en paramètre.

  • Fonction partielle exemple, à l'aide de 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)))
    
  • La création d'un nouveau champ d'application exemple:

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

    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 l' pet_function ou scoped_cage fonction dans la boucle, la compilation a lieu seulement une fois, pas à chaque itération de la boucle.

12voto

Nicolas Barbey Points 2606

Ma compréhension est que la cage est recherché dans le parent de l'espace de noms de fonction lorsque le donné pet_function est en fait appelé, pas avant.

Ainsi, lorsque vous vous

funs = list(get_petters())

Vous générez des 3 fonctions qui va trouver la enfin créé cage.

Si vous remplacez votre dernière boucle avec :

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

Vous réellement obtenir :

cow: Mary pets the cow.
dog: Mary pets the dog.
cat: Mary pets the cat.

7voto

Andy Hayden Points 38010

Cela s'explique par les éléments suivants

for i in range(2): pass
print i is 1

après itération la valeur de i est paresseusement stockées en tant que sa valeur finale.

En tant que générateur de la fonction de travail (c'est à dire l'impression de chaque valeur dans la tour), mais lors de la transformation d'une liste qu'il soit exécuté sur le générateur, donc tous les appels d' cage (cage.animal) rendement des chats.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X