¿Cómo se accede a otras variables de clase desde una comprensión de lista dentro de la definición de clase? Lo siguiente funciona en Python 2 pero falla en Python 3:
class Foo:
x = 5
y = [x for i in range(1)]
Python 3.2 da el error:
NameError: global name 'x' is not defined
Intentar Foo.x
tampoco funciona. ¿Alguna idea sobre cómo hacer esto en Python 3?
Un ejemplo motivador un poco más complicado:
from collections import namedtuple
class StateDatabase:
State = namedtuple('State', ['name', 'capital'])
db = [State(*args) for args in [
['Alabama', 'Montgomery'],
['Alaska', 'Juneau'],
# ...
]]
En este ejemplo, apply()
habría sido una solución decente, pero lamentablemente se eliminó de Python 3.
python
python-3.x
scope
list-comprehension
python-internals
Mark Lodato
fuente
fuente
NameError: global name 'x' is not defined
en Python 3.2 y 3.3, que es lo que esperaría.Respuestas:
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 :
y en la
class
documentación de la declaración compuesta :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 manipulaFoo.x
también, por ejemplo? Más importante aún, ¿qué significaría eso para las subclases deFoo
? 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:
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.
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)
:Por lo tanto, usar
x
esa expresión no arrojaría un error:Esto solo se aplica al iterable más externo; Si una comprensión tiene múltiples
for
cláusulas, los iterables para lasfor
cláusulas internas se evalúan en el alcance de la comprensión: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:El primero
LOAD_CONST
carga un objeto de código para elFoo
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:El bytecode anterior crea el cuerpo de la clase. La función se ejecuta y el
locals()
espacio de nombres resultante , que contienex
yy
se usa para crear la clase (excepto que no funciona porquex
no está definido como global). Tenga en cuenta que después de almacenar5
enx
, 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, elrange(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:
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: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 usaFOR_ITER
para recorrerlo y crear su salida.Si hubiéramos definido
x
en lafoo
función,x
sería una variable de celda (las celdas se refieren a ámbitos anidados):La
LOAD_DEREF
carga indirectax
de los objetos de celda del objeto de código: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:Entonces, para resumir:
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:La función 'temporal'
y
se puede llamar directamente; lo reemplazamos cuando lo hacemos con su valor de retorno. Su alcance se considera al resolverx
: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: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:fuente
y = (lambda x=x: [x for i in range(1)])()
lambda
son solo funciones anónimas, después de todo.En mi opinión, es un defecto en Python 3. Espero que lo cambien.
Old Way (funciona en 2.7, incluye
NameError: name 'x' is not defined
3+):NOTA: simplemente determinarlo
A.x
no lo resolveríaNueva forma (funciona en 3+):
Debido a que la sintaxis es tan fea, simplemente inicializo todas mis variables de clase en el constructor típicamente
fuente
def
para crear una función).python -c "import IPython;IPython.embed()"
. Ejecute IPython directamente usando sayipython
y el problema desaparecerá.La respuesta aceptada proporciona información excelente, pero parece haber algunas otras arrugas aquí: diferencias entre la comprensión de la lista y las expresiones generadoras. Una demo con la que jugué:
fuente
Este es un error en Python. Las comprensiones se anuncian como equivalentes para bucles for, pero esto no es cierto en las clases. Al menos hasta Python 3.6.6, en una comprensión utilizada en una clase, solo una variable desde fuera de la comprensión es accesible dentro de la comprensión, y debe usarse como el iterador más externo. En una función, esta limitación de alcance no se aplica.
Para ilustrar por qué esto es un error, volvamos al ejemplo original. Esto falla:
Pero esto funciona:
La limitación se indica al final de esta sección en la guía de referencia.
fuente
Dado que el iterador más externo se evalúa en el alcance circundante, podemos usarlo
zip
junto conitertools.repeat
para llevar las dependencias al alcance de la comprensión:También se pueden usar
for
bucles anidados en la comprensión e incluir las dependencias en el iterativo más externo:Para el ejemplo específico del OP:
fuente