Pregunta

¿Cómo funciona exactamente evaluar Python atributos de clase? He tropezado a través de una interesante peculiaridad (en Python 2.5.2) que me gustaría explicó.

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

Aquí está el ejemplo pared hacia abajo. Tenga en cuenta que la única diferencia es que Brie utiliza un generador de expresión, mientras que Cheddar utiliza una lista por 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 generadoras se calculan después del final de la clase, en cuyo momento el alcance ha cambiado. Pero no estoy seguro de por qué la expresión generador no actúa como un cierre y almacenar la referencia a la base en el volumen en la línea.

¿Hay una razón para esto, y si es así, cómo debería estar pensando en la mecánica de evaluación de los atributos de clase?

¿Fue útil?

Solución

Sí, es un poco fiable, esto. Una clase en realidad no introducir un nuevo alcance, que sólo una especie de mira un poco como lo hace; construye como esto exponga la diferencia.

La idea es que cuando se utiliza un generador de expresión 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 está claro que base no está en el ámbito de __generatePowers; una excepción resultados para ambos (a menos que fueras mala suerte de tener también un base global, en cuyo caso se obtiene una equivocación).

Esto no sucede para las listas por comprensión debido a algunos detalles internos sobre cómo están evaluados, sin embargo, que el comportamiento desaparece en Python 3 que se producirá un error igual para ambos casos. Algunos discusión aquí.

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))

Otros consejos

PEP 289 :

  

Después de explorar muchas posibilidades, una   consenso de que los problemas de enlace   fueron difíciles de entender y que los usuarios   debe alentar fuertemente utilizar   expresiones generador dentro de las funciones   que consumen sus argumentos   inmediatamente. Para más compleja   aplicaciones, generador completa   las definiciones son siempre superiores en   términos de ser obvio sobre el alcance,   tiempo de vida, y la unión [6].

     

[6] (1, 2) discusión Patch 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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top