La portée de la classe et la liste, la compréhension d'ensemble ou de dictionnaire, ainsi que les expressions du générateur ne se mélangent pas.
Le pourquoi; ou, le mot officiel à ce sujet
Dans Python 3, les compréhensions de liste ont reçu une portée appropriée (espace de noms local) qui leur est propre, pour empêcher leurs variables locales de se répandre dans la portée environnante (voir Python list comprehension rebind names même après la portée de la compréhension. Est-ce vrai? ). C'est génial lorsque vous utilisez une telle compréhension de liste dans un module ou dans une fonction, mais dans les classes, la portée est un peu, euh, étrange .
Ceci est documenté dans pep 227 :
Les noms de la portée de la classe ne sont pas accessibles. Les noms sont résolus dans la portée de la fonction englobante la plus interne. Si une définition de classe se produit dans une chaîne d'étendues imbriquées, le processus de résolution ignore les définitions de classe.
et dans la class
documentation de la déclaration composée :
La suite de la classe est ensuite exécutée dans un nouveau cadre d'exécution (voir la section Nommage et liaison ), en utilisant un espace de noms local nouvellement créé et l'espace de noms global d'origine. (Habituellement, la suite ne contient que des définitions de fonction.) Lorsque la suite de la classe termine son exécution, son cadre d'exécution est ignoré mais son espace de noms local est enregistré . [4] Un objet de classe est ensuite créé en utilisant la liste d'héritage pour les classes de base et l'espace de noms local enregistré pour le dictionnaire d'attributs.
Soulignez le mien; le cadre d'exécution est la portée temporaire.
Étant donné que la portée est réutilisée en tant qu'attributs sur un objet de classe, le fait de l'autoriser à être utilisée comme une portée non locale conduit également à un comportement indéfini; que se passerait-il si une méthode de classe appelée x
variable de portée imbriquée, la manipule Foo.x
également, par exemple? Plus important encore, qu'est-ce que cela signifierait pour les sous-classes de Foo
? Python doit traiter une portée de classe différemment car elle est très différente d'une portée de fonction.
Dernier point, mais non des moindres, la section de dénomination et de liaison liée dans la documentation du modèle d'exécution mentionne explicitement les étendues de classe:
La portée des noms définis dans un bloc de classe est limitée au bloc de classe; il ne s'étend pas aux blocs de code des méthodes - cela inclut les compréhensions et les expressions génératrices puisqu'elles sont implémentées en utilisant une portée de fonction. Cela signifie que ce qui suit échouera:
class A:
a = 42
b = list(a + i for i in range(10))
Donc, pour résumer: vous ne pouvez pas accéder à la portée de la classe à partir des fonctions, des compréhensions de liste ou des expressions génératrices incluses dans cette portée; ils agissent comme si cette portée n'existait pas. Dans Python 2, les compréhensions de liste ont été implémentées à l'aide d'un raccourci, mais dans Python 3, elles ont leur propre portée de fonction (comme elles auraient dû l'avoir depuis le début) et donc votre exemple est interrompu. D'autres types de compréhension ont leur propre portée quelle que soit la version de Python, donc un exemple similaire avec une compréhension d'ensemble ou de dict serait interrompu dans Python 2.
# Same error, in Python 2 or 3
y = {x: x for i in range(1)}
La (petite) exception; ou, pourquoi une partie peut encore fonctionner
Il y a une partie d'une expression de compréhension ou de générateur qui s'exécute dans la portée environnante, quelle que soit la version de Python. Ce serait l'expression de l'itérable le plus externe. Dans votre exemple, c'est le range(1)
:
y = [x for i in range(1)]
# ^^^^^^^^
Ainsi, utiliser x
dans cette expression ne générerait pas d'erreur:
# Runs fine
y = [i for i in range(x)]
Cela s'applique uniquement à l'itérable le plus externe; si une compréhension a plusieurs for
clauses, les itérables des for
clauses internes sont évaluées dans la portée de la compréhension:
# NameError
y = [i for i in range(1) for j in range(x)]
Cette décision de conception a été prise afin de lancer une erreur au moment de la création de genexp au lieu du temps d'itération lorsque la création de l'itérable le plus externe d'une expression de générateur génère une erreur, ou lorsque l'itérable le plus externe s'avère ne pas être itérable. Les compréhensions partagent ce comportement par souci de cohérence.
Regardant sous le capot; ou bien plus de détails que vous ne l'auriez jamais voulu
Vous pouvez voir tout cela en action en utilisant le dis
module . J'utilise Python 3.3 dans les exemples suivants, car il ajoute des noms qualifiés qui identifient parfaitement les objets de code que nous voulons inspecter. Le bytecode produit est par ailleurs fonctionnellement identique à Python 3.2.
Pour créer une classe, Python prend essentiellement toute la suite qui constitue le corps de la classe (donc tout est mis en retrait d'un niveau plus profond que la 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
Le premier LOAD_CONST
charge un objet de code pour le Foo
corps de la classe, puis le transforme en fonction et l'appelle. Le résultat de cet appel est ensuite utilisé pour créer l'espace de noms de la classe, its __dict__
. Jusqu'ici tout va bien.
La chose à noter ici est que le bytecode contient un objet de code imbriqué; en Python, les définitions de classe, les fonctions, les compréhensions et les générateurs sont tous représentés comme des objets de code qui contiennent non seulement du bytecode, mais également des structures qui représentent des variables locales, des constantes, des variables issues de globaux et des variables issues de la portée imbriquée. Le bytecode compilé fait référence à ces structures et l'interpréteur python sait comment accéder à celles données les bytecodes présentés.
La chose importante à retenir ici est que Python crée ces structures au moment de la compilation; la class
suite est un objet de code ( <code object Foo at 0x10a436030, file "<stdin>", line 2>
) déjà compilé.
Inspectons cet objet de code qui crée le corps de classe lui-même; les objets de code ont une 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 bytecode ci-dessus crée le corps de la classe. La fonction est exécutée et l' locals()
espace de noms résultant , 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). On notera que , après le stockage 5
dans x
, il charge un autre objet de code; c'est la compréhension de la liste; il est enveloppé dans un objet fonction comme l'était le corps de la classe; la fonction créée prend un argument de position, l' range(1)
itérable à utiliser pour son code en boucle, transtypé en itérateur. Comme indiqué dans le bytecode, range(1)
est évalué dans la portée de la classe.
De là, vous pouvez voir que la seule différence entre un objet code pour une fonction ou un générateur, et un objet code pour une compréhension est que ce dernier est exécuté immédiatement lorsque l'objet code parent 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 à la place du bytecode en ligne, 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
Aucun objet de code n'est chargé, à la place une FOR_ITER
boucle est exécutée en ligne. Ainsi, en Python 3.x, le générateur de liste a reçu un objet de code propre, ce qui signifie qu'il a sa propre portée.
Cependant, la compréhension a été compilée avec le reste du code source python lorsque le module ou le script a été chargé pour la première fois par l'interpréteur, et le compilateur ne considère pas une suite de classes comme une portée valide. Toutes les variables référencées dans une compréhension de liste doivent regarder dans la portée entourant la définition de classe, de manière récursive. Si la variable n'a pas été trouvée par le compilateur, il la marque comme une variable globale. Le démontage de l'objet de code de compréhension de liste montre qu'il x
est en effet chargé comme un global:
>>> 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 bytecode charge le premier argument passé (l' range(1)
itérateur), et tout comme la version Python 2.x l'utilise FOR_ITER
pour faire une boucle dessus et créer sa sortie.
Si nous avions défini x
dans la foo
fonction à la place, x
serait une variable de cellule (les cellules font référence à des portées imbriquées):
>>> 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
Le LOAD_DEREF
chargera indirectement à x
partir des objets de cellule de l'objet de code:
>>> 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]
Le référencement réel recherche la valeur à partir des structures de données de trame actuelles, qui ont été initialisées à partir de l' .__closure__
attribut d'un objet fonction . Puisque la fonction créée pour l'objet de code de compréhension est à nouveau supprimée, nous ne pouvons pas inspecter la fermeture de cette fonction. Pour voir une fermeture en action, nous devrions plutôt inspecter une fonction imbriquée:
>>> 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:
- Les compréhensions de liste obtiennent leurs propres objets de code dans Python 3, et il n'y a aucune différence entre les objets de code pour les fonctions, les générateurs ou les compréhensions; Les objets de code de compréhension sont enveloppés dans un objet fonction temporaire et appelés immédiatement.
- Les objets de code sont créés au moment de la compilation et toutes les variables non locales sont marquées comme globales ou comme variables libres, en fonction des portées imbriquées du code. Le corps de la classe n'est pas considéré comme une portée pour rechercher ces variables.
- Lors de l'exécution du code, Python n'a qu'à regarder dans les globaux ou la fermeture de l'objet en cours d'exécution. Étant donné que le compilateur n'a pas inclus le corps de la classe comme portée, l'espace de noms de la fonction temporaire n'est pas pris en compte.
Une solution de contournement; ou, que faire à ce sujet
Si vous deviez créer une portée explicite pour la x
variable, comme dans une fonction, vous pouvez utiliser des variables de portée de classe 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]
La fonction «temporaire» y
peut être appelée directement; nous le remplaçons lorsque nous le faisons avec sa valeur de retour. Sa portée est prise en compte lors de la résolution 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 qui liront votre code se gratteront un peu la tête à ce sujet; vous voudrez peut-être y mettre un gros commentaire expliquant pourquoi vous faites cela.
La meilleure solution consiste simplement __init__
à créer une variable d'instance à la place:
def __init__(self):
self.y = [self.x for i in range(1)]
et évitez tous les grattages de tête et les questions pour vous expliquer. Pour votre propre exemple concret, je ne conserverais même pas le namedtuple
sur la classe; soit utilisez la sortie directement (ne stockez pas du tout la classe générée), ou utilisez un global:
from collections import namedtuple
State = namedtuple('State', ['name', 'capital'])
class StateDatabase:
db = [State(*args) for args in [
('Alabama', 'Montgomery'),
('Alaska', 'Juneau'),
# ...
]]
NameError: global name 'x' is not defined
Python 3.2 et 3.3, ce à quoi je m'attendais.