2009-11-20 22 views
15

¿Cómo exactamente Python evalúa los atributos de clase? Me encontré con una peculiaridad interesante (en Python 2.5.2) que me gustaría explicar.Evaluación de atributos de clase y generadores

Tengo una clase con algunos atributos que se definen en términos de otros atributos previamente definidos. Cuando intento usar un objeto generador, Python arroja un error, pero si uso una comprensión simple de la lista ordinaria, no hay problema.

Aquí está el ejemplo recortado. Tenga en cuenta que la única diferencia es que Brie usa una expresión de generador, mientras que Cheddar usa una lista de comprensión.

# 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] 

(Mi caso real fue más complicado, y yo estaba creando un diccionario, pero este es el ejemplo mínimo que pude encontrar.)

Mi única suposición es que las listas por comprensión se calculan en esa línea , pero las expresiones del generador se calculan después del final de la clase, en cuyo punto el alcance ha cambiado. Pero no estoy seguro de por qué la expresión del generador no actúa como cierre y almacena la referencia a la base en el alcance de la línea.

¿Hay alguna razón para esto? De ser así, ¿cómo debería pensar en la mecánica de evaluación de los atributos de clase?

Respuesta

14

Sí, es un poco dudoso, esto. Una clase realmente no presenta un nuevo alcance, simplemente se ve un poco como lo hace; construcciones como esta exponen la diferencia.

La idea es que cuando se está utilizando una expresión generadora es equivalente a hacerlo con un lambda:

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

o explícitamente como una sentencia de función:

class Brie(object): 
    base= 2 

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

    powers= list(__generatePowers()) 

En este caso se trata de claro que base no está en el alcance de __generatePowers; una excepción para ambos (a menos que haya tenido la mala suerte de tener también un base global, en cuyo caso obtendrá un error).

Esto no ocurre para las listas de comprensión debido a algunos detalles internos sobre cómo se evalúan, sin embargo ese comportamiento desaparece en Python 3, que fallará igualmente en ambos casos. Some discussion here.

Una solución alternativa se puede tener el uso de un lambda con la misma técnica que se basó en la espalda en los malos días antes nested_scopes:

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

De PEP 289:

Después de explorar muchas posibilidades, un consenso surgió que los problemas vinculantes eran difíciles de entender y que los usuarios deben ser fuertemente alentados a utilizar expresiones generadoras dentro de las funciones que consumen sus argumentos inmediatamente. Para las aplicaciones más complejas , las definiciones de generador completo siempre son superiores en términos de ser obvio sobre el alcance, de por vida y vinculante [6].

[6] (1, 2) discusión de parches y parches alternativos en Source Forge http://www.python.org/sf/872326

Es la forma de las expresiones generadoras están en el ámbito por lo que he podido averiguar.

Cuestiones relacionadas