2009-11-20 2 views
15

Comment exactement Python évalue-t-il les attributs de classe? Je suis tombé sur une bizarrerie intéressante (en Python 2.5.2) que j'aimerais expliquer.Évaluation d'attribut de classe et générateurs

J'ai une classe avec certains attributs qui sont définis en termes d'autres attributs précédemment définis. Lorsque j'essaie d'utiliser un objet générateur, Python renvoie une erreur, mais si j'utilise une simple compréhension de liste ordinaire, il n'y a pas de problème.

Voici l'exemple simplifié. Notez que la seule différence est que Brie utilise une expression de générateur, alors que Cheddar utilise une compréhension de liste.

# Using a generator expression as the argument to list() fails 
>>> class Brie : 
...  base = 2 
...  powers = list(base**i for i in xrange(5)) 
... 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 3, in Brie 
    File "<stdin>", line 3, in <genexpr> 
NameError: global name 'base' is not defined 

# Using a list comprehension works 
>>> class Cheddar : 
...  base = 2 
...  powers = [base**i for i in xrange(5)] 
... 
>>> Cheddar.powers 
[1, 2, 4, 8, 16] 

# Using a list comprehension as the argument to list() works 
>>> class Edam : 
...  base = 2 
...  powers = list([base**i for i in xrange(5)]) 
... 
>>> Edam.powers 
[1, 2, 4, 8, 16] 

(Mon cas réel est plus complexe, et je crée un dict, mais cela est l'exemple minimum que je pouvais trouver.)

Ma seule conjecture est que la liste compréhensions sont calculés à cette ligne , mais les expressions de générateur sont calculées après la fin de la classe, à quel point la portée a changé. Mais je ne sais pas pourquoi l'expression du générateur n'agit pas comme une fermeture et stocke la référence à la base dans la portée à la ligne.

Y at-il une raison à cela, et si oui, comment devrais-je penser à la mécanique d'évaluation des attributs de classe?

Répondre

14

Oui, c'est un peu douteux, ceci. Une classe n'introduit pas vraiment une nouvelle portée, elle ressemble juste un peu à ce qu'elle fait; construit comme ceci expose la différence.

L'idée est que lorsque vous utilisez une expression du générateur, il est équivalent à le faire avec un lambda:

class Brie(object): 
    base= 2 
    powers= map(lambda i: base**i, xrange(5)) 

ou explicitement comme une déclaration de fonction:

class Brie(object): 
    base= 2 

    def __generatePowers(): 
     for i in xrange(5): 
      yield base**i 

    powers= list(__generatePowers()) 

Dans ce cas, il est il est clair que base ne fait pas partie du domaine __generatePowers; une exception résulte pour les deux (sauf si vous avez eu la malchance d'avoir aussi un base global, auquel cas vous obtenez une erreur). Cela ne se produit pas pour les listes de compréhension en raison de certains détails internes sur la façon dont ils sont évalués, mais ce comportement disparaît dans Python 3 qui échouera également dans les deux cas. Some discussion here.

Une solution peut être HAD avec un lambda avec la même technique que nous comptions sur le dos dans les mauvais jours avant nested_scopes:

class Brie(object): 
    base= 2 
    powers= map(lambda i, base= base: base**i, xrange(5)) 
1

De PEP 289:

Après avoir exploré de nombreuses possibilités, un consensus a émergé que les problèmes de liaison étaient difficiles à comprendre et que les utilisateurs devraient être fortement encouragés à utiliser expressions de générateur à l'intérieur des fonctions qui consomment leurs arguments immédiatement. Pour les applications plus complexes , les définitions du générateur complet sont toujours supérieures en termes d'être évident sur la portée, durée de vie, et la liaison [6].

[6] (1, 2) discussion de Patch et d'autres correctifs sur Source Forge http://www.python.org/sf/872326

Il est comment les expressions du générateur sont scope pour autant que je peux faire sortir.