Comment accéder à d'autres variables de classe à partir d'une compréhension de liste dans la définition de classe? Ce qui suit fonctionne dans Python 2 mais échoue dans 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
Essayer Foo.x
ne fonctionne pas non plus. Des idées sur la façon de faire cela dans Python 3?
Un exemple motivant un peu plus compliqué:
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()
cela aurait été une solution de contournement décente, mais il est malheureusement supprimé de Python 3.
python
python-3.x
scope
list-comprehension
python-internals
Mark Lodato
la source
la source
NameError: global name 'x' is not defined
Python 3.2 et 3.3, ce à quoi je m'attendais.Réponses:
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 :
et dans la
class
documentation de la déclaration composée :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 manipuleFoo.x
également, par exemple? Plus important encore, qu'est-ce que cela signifierait pour les sous-classes deFoo
? 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:
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.
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)
:Ainsi, utiliser
x
dans cette expression ne générerait pas d'erreur:Cela s'applique uniquement à l'itérable le plus externe; si une compréhension a plusieurs
for
clauses, les itérables desfor
clauses internes sont évaluées dans la portée de la compréhension: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:Le premier
LOAD_CONST
charge un objet de code pour leFoo
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: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 , contenantx
ety
est utilisé pour créer la classe (sauf que cela ne fonctionne pas carx
n'est pas défini comme un global). On notera que , après le stockage5
dansx
, 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:
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:Ce morceau de bytecode charge le premier argument passé (l'
range(1)
itérateur), et tout comme la version Python 2.x l'utiliseFOR_ITER
pour faire une boucle dessus et créer sa sortie.Si nous avions défini
x
dans lafoo
fonction à la place,x
serait une variable de cellule (les cellules font référence à des portées imbriquées):Le
LOAD_DEREF
chargera indirectement àx
partir des objets de cellule de l'objet de code: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:Donc, pour résumer:
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: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ésolutionx
: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: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:la source
y = (lambda x=x: [x for i in range(1)])()
lambda
sont que des fonctions anonymes, après tout.À mon avis, c'est une faille dans Python 3. J'espère qu'ils le changeront.
Old Way (fonctionne en 2.7, lance
NameError: name 'x' is not defined
3+):REMARQUE: il suffit de le cadrer avec
A.x
ne le résoudrait pasNew Way (fonctionne en 3+):
Parce que la syntaxe est si moche, je viens d'initialiser toutes mes variables de classe dans le constructeur généralement
la source
def
pour créer une fonction).python -c "import IPython;IPython.embed()"
. Exécutez IPython directement en utilisant sayipython
et le problème disparaîtra.La réponse acceptée fournit d'excellentes informations, mais il semble y avoir quelques autres défauts ici - des différences entre la compréhension de liste et les expressions génératrices. Une démo avec laquelle j'ai joué:
la source
C'est un bogue en Python. Les compréhensions sont annoncées comme étant équivalentes aux boucles for, mais ce n'est pas le cas dans les classes. Au moins jusqu'à Python 3.6.6, dans une compréhension utilisée dans une classe, une seule variable extérieure à la compréhension est accessible à l'intérieur de la compréhension, et elle doit être utilisée comme itérateur le plus extérieur. Dans une fonction, cette limitation de portée ne s'applique pas.
Pour illustrer pourquoi il s'agit d'un bogue, revenons à l'exemple d'origine. Cela échoue:
Mais cela fonctionne:
La limitation est indiquée à la fin de cette section dans le guide de référence.
la source
Puisque l'itérateur le plus externe est évalué dans la portée environnante, nous pouvons utiliser
zip
avecitertools.repeat
pour transférer les dépendances dans la portée de la compréhension:On peut également utiliser des
for
boucles imbriquées dans la compréhension et inclure les dépendances dans l'itérable le plus externe:Pour l'exemple spécifique de l'OP:
la source