web-dev-qa-db-fra.com

Création d'une classe dans une fonction et accès à une fonction définie dans la portée de la fonction conteneur

Modifier :

Voir ma réponse complète au bas de cette question.

tl; dr answer : Python a des étendues imbriquées statiquement. Le static l'aspect peut interagir avec les déclarations de variables implicites, donnant des résultats non évidents.

(Cela peut être particulièrement surprenant en raison de la nature généralement dynamique de la langue).

Je pensais avoir une assez bonne maîtrise des règles de portée de Python, mais ce problème m'a complètement contrecarré, et mon google-fu m'a échoué (pas que je sois surpris - regardez le titre de la question;)

Je vais commencer par quelques exemples qui fonctionnent comme prévu, mais n'hésitez pas à passer à l'exemple 4 pour la partie juteuse.

Exemple 1.

>>> x = 3
>>> class MyClass(object):
...     x = x
... 
>>> MyClass.x
3

Assez simple: lors de la définition de la classe, nous pouvons accéder aux variables définies dans la portée externe (dans ce cas, globale).

Exemple 2.

>>> def mymethod(self):
...     return self.x
... 
>>> x = 3
>>> class MyClass(object):
...     x = x
...     mymethod = mymethod
...
>>> MyClass().mymethod()
3

Encore une fois (en ignorant pour le moment pourquoi on pourrait vouloir faire ça), il n'y a rien d'inattendu ici: nous pouvons accéder aux fonctions dans la portée externe.

Remarque : comme Frédéric l'a souligné ci-dessous, cette fonction ne semble pas fonctionner. Voir l'exemple 5 (et au-delà) à la place.

Exemple 3.

>>> def myfunc():
...     x = 3
...     class MyClass(object):
...         x = x
...     return MyClass
... 
>>> myfunc().x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in myfunc
  File "<stdin>", line 4, in MyClass
NameError: name 'x' is not defined

C'est essentiellement la même chose que l'exemple 1: nous accédons à la portée externe depuis la définition de classe, mais cette fois cette portée n'est pas globale, grâce à myfunc().

Edit 5: Comme @ user3022222 l'a souligné ci-dessous , j'ai bâclé cet exemple dans mon message d'origine. Je crois que cela échoue car seules les fonctions (pas les autres blocs de code, comme cette définition de classe) peuvent accéder aux variables dans la portée englobante. Pour les blocs de code non fonctionnels, seules les variables locales, globales et intégrées sont accessibles. Une explication plus approfondie est disponible dans cette question

Un de plus:

Exemple 4.

>>> def my_defining_func():
...     def mymethod(self):
...         return self.y
...     class MyClass(object):
...         mymethod = mymethod
...         y = 3
...     return MyClass
... 
>>> my_defining_func()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in my_defining_func
  File "<stdin>", line 5, in MyClass
NameError: name 'mymethod' is not defined

Euh ... excusez-moi?

Qu'est-ce qui diffère de l'exemple 2?

Je suis complètement confus. Veuillez me trier. Merci!

P.S. au cas où ce ne serait pas seulement un problème de compréhension, j'ai essayé ceci sur Python 2.5.2 et Python 2.6.2 . Malheureusement, ce sont tout ce à quoi j'ai accès en ce moment, mais ils ont tous les deux le même comportement.

Modifier selon http://docs.python.org/tutorial/classes.html#python-scopes-and-namespaces =: à tout moment de l'exécution, il existe au moins trois étendues imbriquées dont les espaces de noms sont directement accessibles:

  • la portée la plus intérieure, recherchée en premier, contient les noms locaux
  • les étendues de toutes les fonctions englobantes, qui sont recherchées en commençant par l'étendue englobante la plus proche, contiennent des noms non locaux, mais également non globaux
  • l'avant-dernière portée contient les noms globaux du module actuel
  • la portée la plus externe (recherchée en dernier) est l'espace de noms contenant les noms intégrés

# 4. semble être un contre-exemple pour le second.

Édition 2

Exemple 5.

>>> def fun1():
...     x = 3
...     def fun2():
...         print x
...     return fun2
... 
>>> fun1()()
3

Modifier 3

Comme l'a souligné @ Frédéric, l'affectation de à une variable du même nom que dans le domaine externe semble "masquer" la variable externe, empêchant ainsi l'affectation de fonctionner.

Donc, cette version modifiée de l'exemple 4 fonctionne:

def my_defining_func():
    def mymethod_outer(self):
        return self.y
    class MyClass(object):
        mymethod = mymethod_outer
        y = 3
    return MyClass

my_defining_func()

Mais cela ne veut pas:

def my_defining_func():
    def mymethod(self):
        return self.y
    class MyClass(object):
        mymethod_temp = mymethod
        mymethod = mymethod_temp
        y = 3
    return MyClass

my_defining_func()

Je ne comprends toujours pas pourquoi ce masquage se produit: la liaison de nom ne devrait-elle pas se produire lors de l'affectation?

Cet exemple fournit au moins un indice (et un message d'erreur plus utile):

>>> def my_defining_func():
...     x = 3
...     def my_inner_func():
...         x = x
...         return x
...     return my_inner_func
... 
>>> my_defining_func()()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in my_inner_func
UnboundLocalError: local variable 'x' referenced before assignment
>>> my_defining_func()
<function my_inner_func at 0xb755e6f4>

Il apparaît donc que la variable locale est définie lors de la création de la fonction (qui réussit), ce qui fait que le nom local est "réservé" et masque ainsi le nom de portée externe lorsque la fonction est appelée.

Intéressant.

Merci Frédéric pour la (les) réponse (s)!

Pour référence, à partir de les python docs :

Il est important de réaliser que les étendues sont déterminées textuellement: l'étendue globale d'une fonction définie dans un module est l'espace de noms de ce module, peu importe d'où ou par quel alias la fonction est appelée. D'un autre côté, la recherche réelle de noms se fait dynamiquement, au moment de l'exécution - cependant, la définition du langage évolue vers la résolution de noms statique, au moment de la "compilation", alors ne vous fiez pas à la résolution de noms dynamique! (En fait, les variables locales sont déjà déterminées statiquement.)

Édition 4

La vraie réponse

Ce comportement apparemment déroutant est provoqué par Python étendues imbriquées statiquement telles que définies dans PEP 227 . Cela n'a en fait rien à voir avec PEP 3104 .

Depuis le PEP 227:

Les règles de résolution de noms sont typiques des langues à portée statique [...] [sauf que] les variables ne sont pas déclarées. Si une opération de liaison de nom se produit n'importe où dans une fonction, ce nom est alors traité comme local pour la fonction et toutes les références font référence à la liaison locale. Si une référence se produit avant la liaison du nom, une erreur NameError est déclenchée.

[...]

Un exemple de Tim Peters montre les pièges potentiels des étendues imbriquées en l'absence de déclarations:

i = 6
def f(x):
    def g():
        print i
    # ...
    # skip to the next page
    # ...
    for i in x:  # ah, i *is* local to f, so this is what g sees
        pass
    g()

L'appel à g() fera référence à la variable i liée dans f() par la boucle for. Si g() est appelé avant l'exécution de la boucle, une erreur NameError sera déclenchée.

Permet d'exécuter deux versions plus simples de l'exemple de Tim:

>>> i = 6
>>> def f(x):
...     def g():
...             print i
...     # ...
...     # later
...     # ...
...     i = x
...     g()
... 
>>> f(3)
3

lorsque g() ne trouve pas i dans sa portée interne, il recherche dynamiquement vers l'extérieur, trouvant la i dans la portée de f, qui a été liée à 3 Via l'affectation i = x.

Mais changer l'ordre des deux dernières instructions dans f provoque une erreur:

>>> i = 6
>>> def f(x):
...     def g():
...             print i
...     # ...
...     # later
...     # ...
...     g()
...     i = x  # Note: I've swapped places
... 
>>> f(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in f
  File "<stdin>", line 3, in g
NameError: free variable 'i' referenced before assignment in enclosing scope

Se rappelant que le PEP 227 a dit "Les règles de résolution de noms sont typiques pour les langages à portée statique", regardons l'offre de version C (semi-) équivalente:

// nested.c
#include <stdio.h>

int i = 6;
void f(int x){
    int i;  // <--- implicit in the python code above
    void g(){
        printf("%d\n",i);
    }
    g();
    i = x;
    g();
}

int main(void){
    f(3);
}

compiler et exécuter:

$ gcc nested.c -o nested
$ ./nested 
134520820
3

Ainsi, alors que C utilisera avec plaisir une variable non liée (en utilisant tout ce qui s'y trouve auparavant: 134520820, dans ce cas), Python (heureusement) refuse.

Comme note intéressante, les étendues imbriquées statiquement permettent ce que Alex Martelli a appelé "l'optimisation la plus importante que le compilateur Python fait: les variables locales d'une fonction ne sont pas conservées dans un dict, ils sont dans un vecteur de valeurs serré, et chaque accès à une variable locale utilise l'index dans ce vecteur, pas une recherche de nom. "

60
Gabriel Grant

C'est un artefact des règles de résolution de noms de Python: vous avez uniquement accès aux étendues globale et locale, mais pas aux étendues intermédiaires, par exemple pas à votre portée extérieure immédiate.

EDIT: Ce qui précède était mal formulé, vous faites avez accès aux variables définies dans les étendues externes, mais en faisant x = x Ou mymethod = mymethod À partir d'un espace de noms non global, vous masquez en fait la variable externe avec celle que vous définissez localement.

Dans l'exemple 2, votre portée extérieure immédiate est la portée globale, donc MyClass peut voir mymethod, mais dans l'exemple 4 votre portée extérieure immédiate est my_defining_func(), donc elle peut ' t, car la définition externe de mymethod est déjà masquée par sa définition locale.

Voir PEP 3104 pour plus de détails sur la résolution de noms non locaux.

Notez également que, pour les raisons expliquées ci-dessus, je ne peux pas faire fonctionner l'exemple 3 sous Python 2.6.5 ou 3.1.2:

>>> def myfunc():
...     x = 3
...     class MyClass(object):
...         x = x
...     return MyClass
... 
>>> myfunc().x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in myfunc
  File "<stdin>", line 4, in MyClass
NameError: name 'x' is not defined

Mais ce qui suit fonctionnerait:

>>> def myfunc():
...     x = 3
...     class MyClass(object):
...         y = x
...     return MyClass
... 
>>> myfunc().y
3
20
Frédéric Hamidi

Cet article date de quelques années, mais il est parmi les rares à discuter de l'important problème de portée et de liaison statique en Python. Cependant, il y a un malentendu important de l'auteur, par exemple 3, qui pourrait dérouter les lecteurs. (ne prenez pas pour acquis que les autres sont tous corrects, c'est juste que j'ai seulement examiné en détail les problèmes soulevés par l'exemple 3). Permettez-moi de clarifier ce qui s'est passé.

Dans l'exemple 3

def myfunc():
    x = 3
    class MyClass(object):
        x = x
    return MyClass

>>> myfunc().x

doit renvoyer une erreur, contrairement à ce que l'auteur de l'article a dit. Je crois qu'il a raté l'erreur parce que dans l'exemple 1 x a été affecté à 3 dans le périmètre global. Ainsi une mauvaise compréhension de ce qui s'est passé.

L'explication est largement décrite dans cet article Comment les références aux variables sont résolues en Python

6
user3022222