El alcance de la clase y las comprensiones de listas, conjuntos o diccionarios, así como las expresiones generadoras no se mezclan.
El porque; o la palabra oficial sobre esto
En Python 3, las comprensiones de listas recibieron un alcance propio (espacio de nombres local) propio, para evitar que sus variables locales se desborden en el alcance circundante (ver Comprensión de listas de Python volver a unir nombres incluso después del alcance de la comprensión. ¿Es esto correcto? ). Eso es genial cuando se usa dicha comprensión de lista en un módulo o en una función, pero en las clases, el alcance es un poco, uhm, extraño .
Esto está documentado en pep 227 :
Los nombres en el alcance de la clase no son accesibles. Los nombres se resuelven en el ámbito de la función de encierro más interna. Si se produce una definición de clase en una cadena de ámbitos anidados, el proceso de resolución omite las definiciones de clase.
y en la class
documentación de la declaración compuesta :
La suite de la clase se ejecuta en un nuevo marco de ejecución (consulte la sección Nombramiento y enlace ), utilizando un espacio de nombres local recién creado y el espacio de nombres global original. (Por lo general, el conjunto contiene solo definiciones de funciones). Cuando el conjunto de la clase finaliza la ejecución, su marco de ejecución se descarta pero se guarda su espacio de nombres local . [4] Luego se crea un objeto de clase utilizando la lista de herencia para las clases base y el espacio de nombres local guardado para el diccionario de atributos.
El énfasis es mío; El marco de ejecución es el alcance temporal.
Debido a que el alcance se reutiliza como los atributos en un objeto de clase, permitir que se use como un alcance no local también conduce a un comportamiento indefinido; ¿Qué pasaría si un método de clase denominado x
variable de ámbito anidado, luego manipula Foo.x
también, por ejemplo? Más importante aún, ¿qué significaría eso para las subclases de Foo
? Python tiene que tratar un alcance de clase de manera diferente ya que es muy diferente del alcance de una función.
Por último, pero definitivamente no menos importante, la sección de nomenclatura y enlace vinculada en la documentación del modelo de ejecución menciona los ámbitos de clase explícitamente:
El alcance de los nombres definidos en un bloque de clase se limita al bloque de clase; no se extiende a los bloques de código de métodos, esto incluye comprensiones y expresiones generadoras, ya que se implementan utilizando un alcance de función. Esto significa que lo siguiente fallará:
class A:
a = 42
b = list(a + i for i in range(10))
Entonces, para resumir: no puede acceder al ámbito de clase desde funciones, listas de comprensiones o expresiones generadoras incluidas en ese ámbito; actúan como si ese alcance no existiera. En Python 2, las comprensiones de listas se implementaron usando un acceso directo, pero en Python 3 obtuvieron su propio alcance de función (como deberían haber tenido todo el tiempo) y, por lo tanto, su ejemplo se rompe. Otros tipos de comprensión tienen su propio alcance, independientemente de la versión de Python, por lo que un ejemplo similar con una comprensión establecida o dictada se rompería en Python 2.
# Same error, in Python 2 or 3
y = {x: x for i in range(1)}
La (pequeña) excepción; o, por qué una parte aún puede funcionar
Hay una parte de una expresión de comprensión o generador que se ejecuta en el ámbito circundante, independientemente de la versión de Python. Esa sería la expresión del iterable más externo. En su ejemplo, es el range(1)
:
y = [x for i in range(1)]
# ^^^^^^^^
Por lo tanto, usar x
esa expresión no arrojaría un error:
# Runs fine
y = [i for i in range(x)]
Esto solo se aplica al iterable más externo; Si una comprensión tiene múltiples for
cláusulas, los iterables para las for
cláusulas internas se evalúan en el alcance de la comprensión:
# NameError
y = [i for i in range(1) for j in range(x)]
Esta decisión de diseño se tomó con el fin de arrojar un error en el momento de la creación de genexp en lugar del tiempo de iteración cuando se crea el iterable más externo de una expresión generadora, o cuando el iterable más externo resulta no ser iterable. Las comprensiones comparten este comportamiento para mantener la coherencia.
Mirando debajo del capó; o, mucho más detalle del que siempre quisiste
Puedes ver todo esto en acción usando el dis
módulo . Estoy usando Python 3.3 en los siguientes ejemplos, porque agrega nombres calificados que identifican perfectamente los objetos de código que queremos inspeccionar. El código de bytes producido es funcionalmente idéntico a Python 3.2.
Para crear una clase, Python esencialmente toma todo el conjunto que conforma el cuerpo de la clase (por lo que todo sangra un nivel más profundo que la class <name>:
línea), y lo ejecuta como si fuera una función:
>>> 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
El primero LOAD_CONST
carga un objeto de código para el Foo
cuerpo de la clase, luego lo convierte en una función y lo llama. El resultado de esa llamada se usa para crear el espacio de nombres de la clase, its __dict__
. Hasta aquí todo bien.
Lo que hay que tener en cuenta aquí es que el código de bytes contiene un objeto de código anidado; en Python, las definiciones de clase, funciones, comprensiones y generadores se representan como objetos de código que contienen no solo bytecode, sino también estructuras que representan variables locales, constantes, variables tomadas de globales y variables tomadas del ámbito anidado. El código de bytes compilado se refiere a esas estructuras y el intérprete de Python sabe cómo acceder a los dados los códigos de bytes presentados.
Lo importante a recordar aquí es que Python crea estas estructuras en tiempo de compilación; la class
suite es un objeto de código ( <code object Foo at 0x10a436030, file "<stdin>", line 2>
) que ya está compilado.
Inspeccionemos ese objeto de código que crea el cuerpo de la clase en sí; Los objetos de código tienen una co_consts
estructura:
>>> 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
El bytecode anterior crea el cuerpo de la clase. La función se ejecuta y el locals()
espacio de nombres resultante , que contiene x
y y
se usa para crear la clase (excepto que no funciona porque x
no está definido como global). Tenga en cuenta que después de almacenar 5
en x
, se carga otro objeto de código; esa es la lista de comprensión; está envuelto en un objeto de función al igual que el cuerpo de la clase; la función creada toma un argumento posicional, el range(1)
iterable para usar para su código de bucle, se convierte en un iterador. Como se muestra en el código de bytes, range(1)
se evalúa en el alcance de la clase.
De esto puede ver que la única diferencia entre un objeto de código para una función o un generador, y un objeto de código para una comprensión es que este último se ejecuta inmediatamente cuando se ejecuta el objeto de código padre; el bytecode simplemente crea una función sobre la marcha y la ejecuta en unos pocos pasos pequeños.
Python 2.x usa código de bytes en línea allí, aquí se emite desde 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
No se carga ningún objeto de código, en su lugar FOR_ITER
se ejecuta un bucle en línea. Entonces, en Python 3.x, el generador de listas recibió un objeto de código propio, lo que significa que tiene su propio alcance.
Sin embargo, la comprensión se compiló junto con el resto del código fuente de Python cuando el intérprete cargó por primera vez el módulo o el script, y el compilador no considera que un conjunto de clases sea un alcance válido. Cualquier variable referenciada en una lista de comprensión debe mirar en el ámbito que rodea la definición de clase, de forma recursiva. Si el compilador no encontró la variable, la marca como global. El desmontaje del objeto de código de comprensión de la lista muestra que, de x
hecho, se carga como 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
Esta porción de código de bytes carga el primer argumento pasado (el range(1)
iterador), y al igual que la versión Python 2.x usa FOR_ITER
para recorrerlo y crear su salida.
Si hubiéramos definido x
en la foo
función, x
sería una variable de celda (las celdas se refieren a ámbitos anidados):
>>> 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
La LOAD_DEREF
carga indirecta x
de los objetos de celda del objeto de código:
>>> 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]
La referencia real busca el valor desde las estructuras de datos de trama actuales, que se inicializaron desde el .__closure__
atributo de un objeto de función . Dado que la función creada para el objeto de código de comprensión se descarta nuevamente, no podemos inspeccionar el cierre de esa función. Para ver un cierre en acción, tendríamos que inspeccionar una función anidada en su lugar:
>>> 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
Entonces, para resumir:
- Las comprensiones de listas obtienen sus propios objetos de código en Python 3, y no hay diferencia entre los objetos de código para funciones, generadores o comprensiones; Los objetos de código de comprensión se envuelven en un objeto de función temporal y se llaman de inmediato.
- Los objetos de código se crean en tiempo de compilación y las variables no locales se marcan como variables globales o libres, según los ámbitos anidados del código. El cuerpo de la clase no se considera un ámbito para buscar esas variables.
- Al ejecutar el código, Python solo tiene que mirar a los globales, o al cierre del objeto que se está ejecutando actualmente. Como el compilador no incluyó el cuerpo de la clase como ámbito, no se considera el espacio de nombres de la función temporal.
Una solución alternativa; o qué hacer al respecto
Si fuera a crear un alcance explícito para la x
variable, como en una función, puede usar variables de alcance de clase para una comprensión de la lista:
>>> class Foo:
... x = 5
... def y(x):
... return [x for i in range(1)]
... y = y(x)
...
>>> Foo.y
[5]
La función 'temporal' y
se puede llamar directamente; lo reemplazamos cuando lo hacemos con su valor de retorno. Su alcance se considera al resolver 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',)
Por supuesto, las personas que leen su código se rascarán un poco la cabeza; es posible que desee poner un comentario grande y gordo explicando por qué está haciendo esto.
La mejor solución es usar __init__
para crear una variable de instancia en su lugar:
def __init__(self):
self.y = [self.x for i in range(1)]
y evite rascarse la cabeza y preguntas para explicarse. Para su propio ejemplo concreto, ni siquiera lo guardaría namedtuple
en la clase; use la salida directamente (no almacene la clase generada en absoluto) o use 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
en Python 3.2 y 3.3, que es lo que esperaría.