240 votes

Pour accéder aux variables de classe d’une compréhension de liste dans la définition de classe

Comment avez-vous accès à d'autres variables de classe à partir d'une compréhension de liste dans la définition de la classe? Les ouvrages suivants, en Python 2, mais ne parvient pas à Python 3:

class Foo:
    x = 5
    y = [x for i in range(1)]

Python 3.2 donne l'erreur:

NameError: global name 'x' is not defined

En essayant Foo.x ne fonctionne pas non plus. Toutes les idées sur la façon de le faire en Python 3?

Un peu plus compliqué de motivation exemple:

from collections import namedtuple
class StateDatabase:
    State = namedtuple('State', ['name', 'capital'])
    db = [State(*args) for args in [
        ['Alabama', 'Montgomery'],
        ['Alaska', 'Juneau'],
        # ...
    ]]

Dans cet exemple, apply() aurait été décent solution de contournement, mais il est malheureusement retiré de Python 3.

342voto

Martijn Pieters Points 271458

Portée de classe et les interprétations de la liste ne se mélangent pas.

Le pourquoi; ou, le mot officiel sur ce

En Python 3, interprétations de la liste ont reçu une bonne portée (espace de noms local) de leur propre, pour empêcher leurs variables locales des saignements dans les environs de la portée (voir la liste Python de compréhension de relier les noms, même après la portée de la compréhension. Est-ce vrai?). Idéal lors de l'utilisation d'une telle compréhension de liste dans un module ou dans une fonction, mais dans les classes, de la portée est un peu, euh, étrange.

Ceci est documenté dans la pep 227:

Les noms en contexte de classe ne sont pas accessibles. Les noms sont résolus dans enfermer les plus secrets portée de la fonction. Si une définition de classe se produit dans une chaîne de imbriquée étendues, le processus de résolution saute définitions de classe.

et dans l' class composé déclaration de la documentation:

La classe la suite est alors exécuté dans un nouveau cadre de l'exécution (voir l'article de Nommage et de liaison), à l'aide d'un local nouvellement créé de l'espace de noms et l'original de l'espace de noms global. (Habituellement, la suite ne contient que des définitions de fonction.) Lorsque la classe est suite a terminé l'exécution, son exécution trame est ignorée, mais son espace de noms local est enregistré. [4] Un objet de classe est alors créé à l'aide de la liste d'héritage pour les classes de base et les sauvés espace de noms local pour l'attribut dictionnaire.

L'accent de la mine; l'exécution est le domaine temporel.

Parce que la portée est redéfini comme les attributs d'un objet de classe, lui permettant d'être utilisé comme un champ d'application ainsi conduit à un comportement indéterminé; ce qui se passerait si une méthode de la classe visée x comme imbriquée portée variable, puis manipule Foo.x ainsi, par exemple? Plus important encore, ce que cela signifierait pour les sous-classes de Foo? Python a à traiter une portée de classe différemment, car il est très différent d'un domaine de la fonction.

Dernier point, mais certainement pas moins, liés de Nommage et de liaison de la section dans le modèle d'Exécution de la documentation mentionne classe étendues de manière explicite:

La portée de noms définis dans une classe de bloc est limitée à la classe block; il ne s'étend pas aux blocs de code de méthodes – ce qui inclut des compréhensions et générateur d'expressions, car ils sont mis en œuvre à l'aide d'une fonction de la portée. Cela signifie que le suivant ne fonctionne pas:

class A:
     a = 42
     b = list(a + i for i in range(10))

Donc, pour résumer: vous ne pouvez pas accéder à la classe étendue de fonctions, les interprétations de la liste ou du générateur d'expressions enfermé dans cette étendue; ils agissent comme si le champ n'existe pas. En Python 2, interprétations de la liste ont été mis en œuvre à l'aide d'un raccourci, mais en Python 3, ils ont obtenu leur propre portée de la fonction (comme ils devraient l'avoir eu tout le long) et donc votre exemple des pauses.

Regarder sous le capot; ou, de façon plus détaillée que vous avez toujours voulu

Vous pouvez voir tout cela en action à l'aide de l' dis module. Je suis à l'aide de Python 3.3 dans les exemples suivants, car il ajoute les noms qualifiés parfaitement identifier le code des objets que nous voulons pour l'inspecter. Le bytecode produit est par ailleurs identique à celles de Python 3.2.

Pour créer une classe, Python est essentiellement l'ensemble de la suite qui constitue le corps de la classe (donc tout mis en retrait d'un niveau plus profond que l' class <name>: ligne), et l'exécute comme s'il s'agissait d'une fonction:

>>> import dis
>>> def foo():
...     class Foo:
...         x = 5
...         y = [x for i in range(1)]
...     return Foo
... 
>>> dis.dis(foo)
  2           0 LOAD_BUILD_CLASS     
              1 LOAD_CONST               1 (<code object Foo at 0x10a436030, file "<stdin>", line 2>) 
              4 LOAD_CONST               2 ('Foo') 
              7 MAKE_FUNCTION            0 
             10 LOAD_CONST               2 ('Foo') 
             13 CALL_FUNCTION            2 (2 positional, 0 keyword pair) 
             16 STORE_FAST               0 (Foo) 

  5          19 LOAD_FAST                0 (Foo) 
             22 RETURN_VALUE         

La première LOAD_CONST il charge un code objet pour l' Foo du corps de classe, fait alors que dans une fonction, et l'appelle. Le résultat de cet appel est ensuite utilisé pour créer de l'espace de noms de la classe, c'est __dict__. So far So good.

La chose à noter ici est que le pseudo-code contient un code imbriqué objet; en Python, les définitions de classes, des fonctions, des compréhensions et des générateurs sont représentés en tant que code d'objets qui contiennent non seulement du bytecode, mais aussi des structures qui représentent les variables locales, les constantes, les variables prises à partir de variables globales et les variables prises à partir de la imbriquée portée. Le code compilé se réfère à ces structures et l'interpréteur python sait comment accéder à ces donné le bytecode présenté.

La chose importante à retenir ici est que Python crée ces structures au moment de la compilation; l' class suite est un code objet (<code object Foo at 0x10a436030, file "<stdin>", line 2>) qui est déjà compilé.

Nous allons inspecter le code objet qui crée le corps de la classe elle-même; code les objets ont un co_consts structure:

>>> foo.__code__.co_consts
(None, <code object Foo at 0x10a436030, file "<stdin>", line 2>, 'Foo')
>>> dis.dis(foo.__code__.co_consts[1])
  2           0 LOAD_FAST                0 (__locals__) 
              3 STORE_LOCALS         
              4 LOAD_NAME                0 (__name__) 
              7 STORE_NAME               1 (__module__) 
             10 LOAD_CONST               0 ('foo.<locals>.Foo') 
             13 STORE_NAME               2 (__qualname__) 

  3          16 LOAD_CONST               1 (5) 
             19 STORE_NAME               3 (x) 

  4          22 LOAD_CONST               2 (<code object <listcomp> at 0x10a385420, file "<stdin>", line 4>) 
             25 LOAD_CONST               3 ('foo.<locals>.Foo.<listcomp>') 
             28 MAKE_FUNCTION            0 
             31 LOAD_NAME                4 (range) 
             34 LOAD_CONST               4 (1) 
             37 CALL_FUNCTION            1 (1 positional, 0 keyword pair) 
             40 GET_ITER             
             41 CALL_FUNCTION            1 (1 positional, 0 keyword pair) 
             44 STORE_NAME               5 (y) 
             47 LOAD_CONST               5 (None) 
             50 RETURN_VALUE         

Le ci-dessus bytecode crée le corps de la classe. La fonction est exécutée et le résultant locals() d'espace de noms, contenant x et y est utilisé pour créer la classe (sauf que cela ne fonctionne pas car x n'est pas défini comme un global). À noter qu'après la stocker 5 en x, il charge un autre code objet; c'est la compréhension de liste; il est encapsulé dans un objet de fonction tout comme le corps de la classe a; à la création de la fonction prend une position d'argument, l' range(1) itérable à utiliser pour la boucle de code, fonte d'un itérateur.

À partir de ce que vous pouvez voir que la seule différence entre un objet code d'une fonction ou d'un générateur et d'un code objet pour une compréhension, c'est que ce dernier est exécuté immédiatement lorsque le parent de l'objet code est exécuté; le bytecode crée simplement une fonction à la volée et l'exécute en quelques petites étapes.

Python 2.x utilise inline bytecode, il y a la place, voici la sortie de Python 2.7:

  2           0 LOAD_NAME                0 (__name__)
              3 STORE_NAME               1 (__module__)

  3           6 LOAD_CONST               0 (5)
              9 STORE_NAME               2 (x)

  4          12 BUILD_LIST               0
             15 LOAD_NAME                3 (range)
             18 LOAD_CONST               1 (1)
             21 CALL_FUNCTION            1
             24 GET_ITER            
        >>   25 FOR_ITER                12 (to 40)
             28 STORE_NAME               4 (i)
             31 LOAD_NAME                2 (x)
             34 LIST_APPEND              2
             37 JUMP_ABSOLUTE           25
        >>   40 STORE_NAME               5 (y)
             43 LOAD_LOCALS         
             44 RETURN_VALUE        

Pas de code objet est chargé, au lieu d'un FOR_ITER boucle est en marche en ligne. Donc en Python 3.x, le générateur de liste a été donné un bon code de l'objet de son propre, ce qui signifie qu'il a son propre champ d'application.

Cependant, la compréhension a été compilé avec le reste du code source python lorsque le module ou script a d'abord été chargé par l'interprète, et le compilateur ne pas envisager une classe suite à une étendue valide. Toutes les variables référencées dans une compréhension de liste doit regarder dans le périmètre entourant la définition de la classe, de façon récursive. Si la variable n'a pas été trouvé par le compilateur, il le marque comme un mondiale. Le démontage de la compréhension de liste de code de l'objet montre qu' x est en effet chargé mondial:

>>> foo.__code__.co_consts[1].co_consts
('foo.<locals>.Foo', 5, <code object <listcomp> at 0x10a385420, file "<stdin>", line 4>, 'foo.<locals>.Foo.<listcomp>', 1, None)
>>> dis.dis(foo.__code__.co_consts[1].co_consts[2])
  4           0 BUILD_LIST               0 
              3 LOAD_FAST                0 (.0) 
        >>    6 FOR_ITER                12 (to 21) 
              9 STORE_FAST               1 (i) 
             12 LOAD_GLOBAL              0 (x) 
             15 LIST_APPEND              2 
             18 JUMP_ABSOLUTE            6 
        >>   21 RETURN_VALUE         

Ce morceau de code binaire en charge le premier argument passé ( range(1) itérateur), et tout comme le Python 2.x version utilise FOR_ITER boucle et créer du sortie de.

Si nous avions définis x dans la foo fonction au lieu de cela, x serait une cellule variable (cellules reportez-vous à imbriqués les étendues):

>>> def foo():
...     x = 2
...     class Foo:
...         x = 5
...         y = [x for i in range(1)]
...     return Foo
... 
>>> dis.dis(foo.__code__.co_consts[2].co_consts[2])
  5           0 BUILD_LIST               0 
              3 LOAD_FAST                0 (.0) 
        >>    6 FOR_ITER                12 (to 21) 
              9 STORE_FAST               1 (i) 
             12 LOAD_DEREF               0 (x) 
             15 LIST_APPEND              2 
             18 JUMP_ABSOLUTE            6 
        >>   21 RETURN_VALUE         

L' LOAD_DEREF d'une manière indirecte, la charge x à partir du code objet des objets de cellule:

>>> foo.__code__.co_cellvars               # foo function `x`
('x',)
>>> foo.__code__.co_consts[2].co_cellvars  # Foo class, no cell variables
()
>>> foo.__code__.co_consts[2].co_consts[2].co_freevars  # Refers to `x` in foo
('x',)
>>> foo().y
[2]

La réelle référencement regarde la valeur de l'image actuelle des structures de données, qui ont été initialisés à partir d'une fonction de l'objet .__closure__ d'attribut. Depuis la fonction créée pour la compréhension du code objet est jeté encore une fois, nous ne sommes pas à contrôler que la fonction de fermeture. Pour voir une fermeture en action, nous aurions à inspecter une fonction imbriquée à la place:

>>> def spam(x):
...     def eggs():
...         return x
...     return eggs
... 
>>> spam(1).__code__.co_freevars
('x',)
>>> spam(1)()
1
>>> spam(1).__closure__
>>> spam(1).__closure__[0].cell_contents
1
>>> spam(5).__closure__[0].cell_contents
5

Donc, pour résumer:

  • Liste des compréhensions obtenir leur propre code d'objets en Python 3, et il n'y a pas de différence entre le code des objets pour les fonctions, des générateurs ou des interprétations de la compréhension du code des objets sont enveloppés dans un temporaire de la fonction de l'objet et appelle immédiatement.
  • Code les objets sont créés au moment de la compilation, et non les variables locales sont marqué soit global ou comme variables sur la base de la imbriqués les étendues du code. Le corps de la classe est pas considéré comme un champ d'application pour la recherche de ces variables.
  • Lors de l'exécution du code, Python n'a qu'à regarder dans les variables globales, ou la fermeture de l'exécution actuelle de l'objet. Puisque le compilateur ne comprend pas le corps de la classe comme une portée, de la temporaire de la fonction de l'espace de noms n'est pas considérée.

Une solution de contournement; ou, quoi faire à ce sujet

Si vous deviez créer une portée explicite pour l' x variable, comme dans une fonction, vous pouvez utiliser la classe-portée des variables pour une compréhension de liste:

>>> class Foo:
...     x = 5
...     def y(x):
...         return [x for i in range(1)]
...     y = y(x)
... 
>>> Foo.y
[5]

Le "temporaire" y fonction peut être appelée directement; nous remplacer quand nous le faisons avec elle la valeur de retour. Son champ d'application est considéré lors de la résolution d' x:

>>> foo.__code__.co_consts[1].co_consts[2]
<code object y at 0x10a5df5d0, file "<stdin>", line 4>
>>> foo.__code__.co_consts[1].co_consts[2].co_cellvars
('x',)

Bien sûr, les gens de la lecture de votre code de rayer leurs têtes sur ce un peu, vous voudrez peut-être mettre un gros commentaire en y expliquant pourquoi vous faites cela.

La meilleure façon de contourner ce qui est juste utiliser __init__ pour créer une variable d'instance à la place:

def __init__(self):
    self.y = [self.x for i in range(1)]

et d'éviter tous les casse-tête, et des questions à vous expliquer. Pour votre propre exemple concret, je n'aurais même pas stocker l' namedtuple sur la classe; soit utiliser directement le résultat (ne pas stocker la classe générée à tous), ou utiliser une approche globale:

from collections import namedtuple
State = namedtuple('State', ['name', 'capital'])

class StateDatabase:
    db = [State(*args) for args in [
       ('Alabama', 'Montgomery'),
       ('Alaska', 'Juneau'),
       # ...
    ]]

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