49 votes

Comment les références aux variables sont résolues en Python

Ce message est un peu long, avec de nombreux exemples, mais j'espère que c' va m'aider et aider les autres à mieux comprendre l'histoire complète de variables et l'attribut de recherche en Python 2.7.

Je suis en utilisant les termes de PEP 227 (http://www.python.org/dev/peps/pep-0227/) pour les blocs de code (comme les modules, la définition de la classe, des définitions de fonction, etc.) et variable liaisons (affectations, par exemple, l'argument de déclarations, de classe et la déclaration de la fonction, pour les boucles, etc.)

Je suis en utilisant les termes de variables pour les noms qui peuvent être appelées sans dot, des attributs et des noms qui ont besoin d'être qualifié avec un objet nom (obj.x pour l'attribut x de l'objet obj).

Il y a trois champs d'application dans Python pour tous les blocs de code, mais les fonctions:

  • Local
  • Mondial
  • Builtin

Il y a quatre blocs en Python pour les fonctions uniquement (selon PEP 227):

  • Local
  • En joignant les fonctions
  • Mondial
  • Builtin

La règle pour une variable à lier et à trouver dans un bloc est très simple:

  • toute liaison d'une variable à un objet dans un bloc fait de cette variable locales à ce bloc, à moins que la variable est déclarée global (dans ce cas de la variable appartient à la portée globale)
  • une référence à une variable est recherché à l'aide de la règle LGB (local, global, intégré) pour tous les blocs, mais les fonctions
  • une référence à une variable est recherché à l'aide de la règle LEGB (local, joignant, global, intégré) pour les fonctions.

Laissez-moi savoir prendre des exemples de validation de cette règle, et, montrant de nombreuses des cas particuliers. Pour chaque exemple, je vais donner ma compréhension. Merci corrigez-moi si je me trompe. Pour le dernier exemple, je ne comprends pas l' résultat.

exemple 1:

x = "x in module"
class A():
    print "A: "  + x                    #x in module
    x = "x in class A"
    print locals()
    class B():
        print "B: " + x                 #x in module
        x = "x in class B"
        print locals()
        def f(self):
            print "f: " + x             #x in module
            self.x = "self.x in f"
            print x, self.x
            print locals()

>>>A.B().f()
A: x in module
{'x': 'x in class A', '__module__': '__main__'}
B: x in module
{'x': 'x in class B', '__module__': '__main__'}
f: x in module
x in module self.x in f
{'self': <__main__.B instance at 0x00000000026FC9C8>}

Il n'est pas imbriquée portée pour les classes (règle LGB) et une fonction dans une classe ne peut pas accéder aux attributs de la classe sans l'aide d'un nom complet (du soi.x dans cet exemple). C'est bien décrite dans PEP227.

exemple 2:

z = "z in module"
def f():
    z = "z in f()"
    class C():
        z = "z in C"
        def g(self):
            print z
            print C.z
    C().g()
f()
>>> 
z in f()
z in C

Ici les variables dans les fonctions de recherche à l'aide de la LEGB la règle, mais si une classe est le chemin, la classe arguments sont ignorés. Ici encore, c'est ce que PEP 227, c'est d'expliquer.

exemple 3:

var = 0
def func():
    print var
    var = 1
>>> func()

Traceback (most recent call last):
  File "<pyshell#102>", line 1, in <module>
func()
  File "C:/Users/aa/Desktop/test2.py", line 25, in func
print var
UnboundLocalError: local variable 'var' referenced before assignment

Nous attendons avec un langage dynamique comme le python que tout est résolu dynamiquement. Mais ce n'est pas le cas pour les fonctions. Local les variables sont déterminées au moment de la compilation. PEP 227 et http://docs.python.org/2.7/reference/executionmodel.html décrire cette le comportement de cette façon

"Si un nom de liaison opération se produit n'importe où à l'intérieur d'un bloc de code, tous les les utilisations de ce nom dans le bloc sont traités comme des références à la bloc courant."

exemple 4:

x = "x in module"
class A():
    print "A: " + x
    x = "x in A"
    print "A: " + x
    print locals()
    del x
    print locals()
    print "A: " + x
>>> 
A: x in module
A: x in A
{'x': 'x in A', '__module__': '__main__'}
{'__module__': '__main__'}
A: x in module

Mais nous voyons ici que cette déclaration dans PEP227 "Si un nom de liaison l'opération se produit n'importe où à l'intérieur d'un bloc de code, toutes les utilisations de ce nom dans le bloc sont traités comme des références pour le bloc en cours." le problème quand le bloc de code est une classe. En outre, pour les classes, il semble que le nom local de la liaison n'est pas faite au moment de la compilation, mais au cours de exécution à l'aide de la classe de l'espace de noms. À cet égard, PEP227 et le modèle d'exécution dans la doc Python est trompeuse et pour certaines parties de mal.

exemple 5:

x = 'x in module'
def  f2():
    x = 'x in f2'
    def myfunc():
        x = 'x in myfunc'
        class MyClass(object):
            x = x
            print x
        return MyClass
    myfunc()
f2()
>>> 
x in module

ma compréhension de ce code est le suivant. L'instruction x = x regardez d'abord l'objet de la main droite x de l'expression se réfère . Dans ce cas, l'objet est recherché localement dans la classe, suivant la règle LGB il est recherché dans le contexte global, qui est la chaîne de caractères 'x dans le module'. Ensuite, un local de l'attribut x de MyClass est créée dans la classe dictionnaire et a souligné l'objet de type string.

exemple 6:

Maintenant ici est un exemple je ne peux pas expliquer. Il est très proche de l'exemple 5, je suis simplement en changeant le local MyClass attribut de x à y.

x = 'x in module'
def  f2():
    x = 'x in f2'
    def myfunc():
        x = 'x in myfunc'
        class MyClass(object):
            y = x
            print y
        return MyClass
    myfunc()
f2()
>>>
x in myfunc

Pourquoi, dans ce cas, le x de référence dans Maclasse est recherché dans le fonction la plus intérieure?

22voto

Martijn Pieters Points 271458

Dans un monde idéal, vous auriez droit et de certaines incohérences que vous avez trouvé serait faux. Cependant, Disponible a optimisé certains scénarios, plus précisément de la fonction habitants. Ces optimisations, ainsi que la façon dont le compilateur et de l'évaluation de la boucle d'interagir et de précédent historique, conduire à la confusion.

Python se traduit par le code de bytecode, et ceux-ci sont ensuite interprétées par un interprète de la boucle. Le cas "normal" opcode pour accéder à un nom est LOAD_NAME, qui ressemble à un nom de variable comme vous le feriez dans un dictionnaire. LOAD_NAME sera d'abord rechercher un nom comme un local, et si cela échoue, recherche un mondial. LOAD_NAME jette un NameError exception lorsque le nom n'est pas trouvé.

Pour imbriquée étendues, en recherchant des noms en dehors de la champ d'application actuel est mis en œuvre à l'aide de fermetures; si un nom n'est pas affecté, mais est disponible dans une étude (non global) champ d'application, puis ces valeurs sont traitées comme une fermeture. C'est nécessaire parce qu'un parent peut contenir des valeurs différentes pour un nom donné à des moments différents; deux appels à une fonction parent peut conduire à de clôture différentes valeurs. Donc, Python a LOAD_CLOSURE, MAKE_CLOSURE et LOAD_DEREF opcodes pour que la situation; les deux premiers opérateurs sont utilisés pour le chargement et la création d'une fermeture pour un imbriquée portée, et l' LOAD_DEREF charge le plus de valeur lorsque le imbriquée à l'étendue des besoins.

Maintenant, LOAD_NAME est relativement lente; elle consultera les deux dictionnaires, ce qui signifie qu'il a de hachage de la clé de la première et exécuter quelques tests d'égalité (si le nom n'était pas internés). Si le nom n'est pas local, il doit alors le faire de nouveau, pour un mondial. Pour les fonctions, qui peuvent potentiellement être appelé des dizaines de milliers de fois, cela peut devenir vite fastidieux. Si la fonction habitants ont de spécial les opcodes. Chargement d'un nom local est mis en œuvre par LOAD_FAST, ce qui ressemble à des variables locales par index dans un local spécial noms de tableau. C'est beaucoup plus rapide, mais il faut que le compilateur doit d'abord voir si un nom est un local et non global. Toujours être en mesure de rechercher des noms globaux, un autre opcode LOAD_GLOBAL est utilisé. Le compilateur explicitement optimise pour ce cas générer la spéciale opcodes. LOAD_FAST va jeter un UnboundLocalError exception quand il n'est pas encore une valeur pour le nom.

Définition de classe d'organes, d'autre part, même s'ils sont traités à peu près comme une fonction, ne pas obtenir cette optimisation de l'étape. Définitions de classe ne sont pas destinés à être appelé tous que, souvent,; la plupart des modules de créer des classes une fois, lors de l'importation. Classe étendues ne comptent pas lorsqu'de nidification, de sorte que les règles sont plus simples. En conséquence, la définition de la classe organes ne pas agir comme des fonctions lorsque vous commencer à mélanger les étendues un peu.

Donc, pour les non-fonction étendues, LOAD_NAME et LOAD_DEREF sont utilisés pour les habitants et globales, et à la fermeture, respectivement. Pour les fonctions, LOAD_FAST, LOAD_GLOBAL et LOAD_DEREF sont utilisés à la place.

Notez que la classe organes sont exécutées dès que Python exécute l' class ligne! Ainsi, dans l'exemple 1, class B à l'intérieur d' class A est exécutée dès qu' class A est exécutée, ce qui est quand vous importez le module. Dans l'exemple 2, C n'est pas exécutée jusqu' f() est appelé, et pas avant.

Permet de marcher à travers votre exemples:

  1. Vous avez une classe imbriquée A.B dans une classe A. Classe organismes ne font pas imbriquée étendues, de sorte que même si l' A.B classe de corps est exécutée lorsque la classe A est exécuté, le compilateur utilisera LOAD_NAME pour rechercher x. A.B().f() est une fonction (lié à l' B() exemple comme méthode), de sorte qu'il utilise LOAD_GLOBAL à la charge x. Nous allons ignorer attribut d'accès ici, c'est très bien défini un modèle de nom.

  2. Ici, f().C.z est à portée de classe, de sorte que la fonction f().C().g() d'ignorer l' C de la portée et de regarder le f() portée au lieu de cela, à l'aide de LOAD_DEREF.

  3. Ici, var a été déterminé à être un local par le compilateur car vous l'attribuer à l'intérieur de la portée. Les fonctions sont optimisés, LOAD_FAST est utilisé pour rechercher le local et une exception est levée.

  4. Maintenant, les choses deviennent un peu bizarre. class A est exécuté à portée de classe, de sorte LOAD_NAME est utilisé. A.x a été supprimé de la population locale dictionnaire pour la portée, de sorte que la seconde l'accès à l' x résultats dans le global x être trouvé à la place; LOAD_NAME cherché un local de première et de ne pas le trouver là, de revenir à la mondiaux de la recherche.

    Oui, cela semble incompatible avec la documentation. Python-la-langue et Disponible-la mise en œuvre s'affrontent un peu ici. Vous êtes, cependant, repoussant les limites de ce qui est possible et pratique, dans un langage dynamique; vérifier si x , aurait été d'un local en LOAD_NAME serait possible, mais prend précieux temps d'exécution pour un coin de cas que la plupart des développeurs ne sera jamais exécuté.

  5. Maintenant, vous êtes à confusion, le compilateur. Vous avez utilisé x = x dans le domaine de la classe, et donc vous êtes la fixation d'un local à partir d'un nom en dehors de la portée. Le compilateur détecte x est un, ici (vous attribuez à elle), de sorte qu'il ne considère jamais qu'il pourrait également être une étendue de nom. Le compilateur utilise LOAD_NAME pour toutes les références à x dans ce champ d'application, parce que ce n'est pas une fonction optimisée corps.

    Lors de l'exécution de la définition de la classe, x = x d'abord vous oblige à regarder jusqu' x, de sorte qu'il utilise LOAD_NAME à le faire. Pas de x est défini, LOAD_NAME ne trouve pas de local, de sorte que le global x est constaté. La valeur obtenue est stockée comme un local, qui arrive à être nommé x . print x utilise LOAD_NAME de nouveau, et se trouve maintenant le nouveau local x de la valeur.

  6. Ici, vous ne confondez pas le compilateur. Vous êtes à la création d'un local y, x n'est pas local, de sorte que le compilateur reconnaît comme une étendue de nom de fonction parent, f2().myfunc(). x se leva LOAD_DEREF à partir de la fermeture, et stockés dans y.

Vous avez pu le voir, la confusion entre 5 et 6 comme un bug, mais celui qui n'est pas la peine de fixation à mon avis. Il a certainement été déposé en tant que tel, voir la question 532860 dans le Python bug tracker, il a été là pendant plus de 10 ans maintenant.

Le compilateur pourrait vérifier une étendue de nom x même lorsqu' x est également un local, pour cette première affectation dans l'exemple 5. Ou LOAD_NAME pourrait vérifier si le nom est censé être un local, vraiment, et de jeter un UnboundLocalError si aucun local a été trouvé, au détriment de la performance. Si cela avait été dans une portée de fonction, LOAD_FAST aurait été utilisé pour l'exemple 5, et un UnboundLocalError pourrait être levée immédiatement.

Cependant, référencé " bug montre, pour des raisons historiques, le comportement est conservé. Il est probablement le code d'aujourd'hui qui va casser ce bug soit corrigé.

19voto

Armin Rigo Points 4754

En deux mots, la différence entre l'exemple 5 et de l'exemple 6 est que dans l'exemple 5, la variable x est également attribué dans le même champ d'application, tout n'est pas dans l'exemple 6. Cela déclenche une différence qui peut être comprise que par des raisons historiques.

Cela soulève UnboundLocalError:

x = "foo"
def f():
    print x
    x = 5
f()

au lieu de l'impression "foo". Il fait un peu de sens, même si cela semble étrange au premier abord: la fonction f() définit la variable x localement, même si c'est après l'impression, et donc toute référence à l' x dans la même fonction doit être de cette variable locale. Au moins il fait sens en ce qu'elle évite d'étranges surprises si vous avez par erreur réutilisé le nom d'une variable globale localement, et essayez d'utiliser à la fois globale et locale de la variable. C'est une bonne idée, car cela signifie que nous pouvons statiquement sais, juste en regardant une variable, dont la variable qu'il signifie. Par exemple, nous savons qu' print x fait référence à la variable locale (et donc peut soulever UnboundLocalError) ici:

x = "foo"
def f():
    if some_condition:
        x = 42
    print x
f()

Maintenant, cette règle ne fonctionne pas pour la classe de niveau étendues: il y a, nous voulons des expressions comme x = x de travail, la capture de la variable globale x dans la classe au niveau de la portée. Cela signifie que le niveau de la classe étendues de ne pas suivre la règle de base ci-dessus: on ne peut pas savoir si x dans ce domaine fait référence à certains extérieur de la variable ou de l'définies localement x --- par exemple:

class X:
    x = x     # we want to read the global x and assign it locally
    bar = x   # but here we want to read the local x of the previous line

class Y:
    if some_condition:
        x = 42
    print x     # may refer to either the local x, or some global x

class Z:
    for i in range(2):
        print x    # prints the global x the 1st time, and 42 the 2nd time
        x = 42

Donc dans la classe, les étendues, une règle différente est utilisée: où il devrait normalement augmenter UnboundLocalError --- et seulement dans ce cas - - - - - cela ressemble plutôt dans le module globals. C'est tout: il n'a pas suivi la chaîne de imbriquée étendues.

Pourquoi pas? En fait je doute qu'il y est une meilleure explication que "pour des raisons historiques". Sur un plan plus technique, on peut considérer que la variable x est à la fois définies localement dans le domaine de la classe (parce qu'il est attribué à) et doit être transmis à partir de la portée parent comme un lexicalement imbriquée variable (car il est en lecture). Il serait possible de la mettre en œuvre en utilisant un autre bytecode qu' LOAD_NAME qui recherche dans le domaine local, et tombe en arrière à l'aide de la imbriquée de la portée de la référence si elle ne trouve pas.

EDIT: merci wilberforce pour la référence à http://bugs.python.org/issue532860. Nous pouvons avoir une chance d'obtenir quelques discussions réactivé avec le projet de nouveau code binaire, si nous estimons qu'il doit être fixé après tout (le rapport de bug considère meurtre de soutien pour x = x , mais a été fermé de peur de briser trop de code existant; à la place, ce que je suggère ici, ce serait le faire x = x de travail en plus des cas). Ou j'ai peut-être manquant un autre bon point...

EDIT2: il semble que Disponible a fait précisément que dans le courant de 3.4 tronc: http://bugs.python.org/issue17853 ... ou pas? Ils ont introduit le pseudo-code d'un peu différente de la raison et de ne pas l'utiliser systématiquement...

7voto

babbageclunk Points 3246

Longue histoire courte, c'est un coin en cas de Python de portée de ce qui est un peu incohérent, mais doit être conservé pour la compatibilité ascendante (et parce que c'est pas clair quelle est la bonne réponse devrait être). Vous pouvez voir beaucoup de la première discussion à ce sujet sur le Python liste de diffusion lorsque PEP 227 a été mis en place, et certains dans le bug pour lequel ce comportement est le correctif.

On peut savoir pourquoi il y a une différence à l'aide de l' dis module, ce qui nous permet de regarder à l'intérieur le code des objets de voir le pseudo-code d'un morceau de code a été compilé. Je suis sur la version 2.6 de Python, de sorte que les détails de ce qui peut être légèrement différent, mais je vois toujours le même comportement, donc je pense que c'est probablement assez proche de 2.7.

Le code qui initialise chaque imbriquée MyClass vit dans un code objet que vous pouvez obtenir via les attributs des fonctions de niveau supérieur. (Je suis de renommer les fonctions à partir de l'exemple 5 et de l'exemple 6 f1 et f2 respectivement).

Le code de l'objet a un co_consts tuple, qui contient l' myfunc code objet, qui à son tour a le code qui s'exécute lorsque l' MyClass vient d'être créé:

In [20]: f1.func_code.co_consts
Out[20]: (None,
 'x in f2',
 <code object myfunc at 0x1773e40, file "<ipython-input-3-6d9550a9ea41>", line 4>)
In [21]: myfunc1_code = f1.func_code.co_consts[2]
In [22]: MyClass1_code = myfunc1_code.co_consts[3]
In [23]: myfunc2_code = f2.func_code.co_consts[2]
In [24]: MyClass2_code = myfunc2_code.co_consts[3]

Ensuite, vous pouvez voir la différence entre eux dans le bytecode à l'aide d' dis.dis:

In [25]: from dis import dis
In [26]: dis(MyClass1_code)
  6           0 LOAD_NAME                0 (__name__)
              3 STORE_NAME               1 (__module__)

  7           6 LOAD_NAME                2 (x)
              9 STORE_NAME               2 (x)

  8          12 LOAD_NAME                2 (x)
             15 PRINT_ITEM          
             16 PRINT_NEWLINE       
             17 LOAD_LOCALS         
             18 RETURN_VALUE        

In [27]: dis(MyClass2_code)
  6           0 LOAD_NAME                0 (__name__)
              3 STORE_NAME               1 (__module__)

  7           6 LOAD_DEREF               0 (x)
              9 STORE_NAME               2 (y)

  8          12 LOAD_NAME                2 (y)
             15 PRINT_ITEM          
             16 PRINT_NEWLINE       
             17 LOAD_LOCALS         
             18 RETURN_VALUE        

Donc, la seule différence est que, en MyClass1, x est chargé à l'aide de l' LOAD_NAME op, tandis que dans d' MyClass2, il est chargé à l'aide d' LOAD_DEREF. LOAD_DEREF recherche d'un nom dans un cadre englobant, de sorte qu'il devient 'x dans mafonction'. LOAD_NAME ne suivent pas imbriquée étendues puisqu'il ne peut pas voir l' x noms liée, en myfunc ou f1, il obtient le niveau du module de liaison.

Alors la question est, pourquoi le code des deux versions de l' MyClass compilé à deux différents opérateurs? En f1 la liaison est l'occultation x dans le domaine de la classe, tandis que dans d' f2 c'est la liaison d'un nouveau nom. Si l' MyClass ont été étendues fonctions imbriquées au lieu de classes, l' y = x ligne f2 serait compilé le même, mais l' x = x en f1 serait un LOAD_FAST - c'est parce que le compilateur sache que x est lié à la fonction, de sorte qu'il devrait utiliser l' LOAD_FAST pour récupérer une variable locale. Ce serait un échec avec un UnboundLocalError quand il a été appelé.

In [28]:  x = 'x in module'
def  f3():
    x = 'x in f2'
    def myfunc():
        x = 'x in myfunc'
        def MyFunc():
            x = x
            print x
        return MyFunc()
    myfunc()
f3()
---------------------------------------------------------------------------
Traceback (most recent call last)
<ipython-input-29-9f04105d64cc> in <module>()
      9         return MyFunc()
     10     myfunc()
---> 11 f3()

<ipython-input-29-9f04105d64cc> in f3()
      8             print x
      9         return MyFunc()
---> 10     myfunc()
     11 f3()

<ipython-input-29-9f04105d64cc> in myfunc()
      7             x = x
      8             print x
----> 9         return MyFunc()
     10     myfunc()
     11 f3()

<ipython-input-29-9f04105d64cc> in MyFunc()
      5         x = 'x in myfunc'
      6         def MyFunc():
----> 7             x = x
      8             print x
      9         return MyFunc()

UnboundLocalError: local variable 'x' referenced before assignment

Cela échoue, car l' MyFunc fonction utilise alors LOAD_FAST:

In [31]: myfunc_code = f3.func_code.co_consts[2]
MyFunc_code = myfunc_code.co_consts[2]
In [33]: dis(MyFunc_code)
  7           0 LOAD_FAST                0 (x)
              3 STORE_FAST               0 (x)

  8           6 LOAD_FAST                0 (x)
              9 PRINT_ITEM          
             10 PRINT_NEWLINE       
             11 LOAD_CONST               0 (None)
             14 RETURN_VALUE        

(En aparté, c'est pas une grande surprise qu'il devrait y avoir une différence dans la façon dont la portée interagit avec le code dans le corps de classes et le code dans une fonction. Vous pouvez dire ce à cause des liaisons au niveau de la classe ne sont pas disponibles dans les méthodes - méthode étendues ne sont pas imbriquée à l'intérieur de l'étendue de classe de la même manière que les fonctions imbriquées sont. Vous devez explicitement les atteindre via la classe, ou à l'aide de self. (ce qui revient à la classe s'il n'y a pas aussi une instance de niveau de liaison).)

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