valor - Cómo se resuelven las referencias a las variables en Python
mostrar resultados en python (3)
En dos palabras, la diferencia entre el ejemplo 5 y el ejemplo 6 es que en el ejemplo 5 la variable x
también se asigna en el mismo ámbito, mientras que no en el ejemplo 6. Esto desencadena una diferencia que puede entenderse por razones históricas.
Esto aumenta UnboundLocalError:
x = "foo"
def f():
print x
x = 5
f()
en lugar de imprimir "foo". Tiene un poco de sentido, aunque parezca extraño al principio: la función f () define la variable x
localmente, incluso si está después de la impresión, por lo que cualquier referencia a x
en la misma función debe ser a esa variable local . Al menos tiene sentido, ya que evita sorpresas extrañas si, por error, reutilizó localmente el nombre de una variable global, y está tratando de usar tanto la variable global como la local. Esta es una buena idea porque significa que podemos saber estáticamente, simplemente mirando una variable, qué variable significa. Por ejemplo, sabemos que print x
refiere a la variable local (y por lo tanto puede elevar UnboundLocalError) aquí:
x = "foo"
def f():
if some_condition:
x = 42
print x
f()
Ahora, esta regla no funciona para los ámbitos de nivel de clase: allí, queremos que expresiones como x = x
funcionen, capturando la variable global x
en el ámbito de nivel de clase. Esto significa que los ámbitos de nivel de clase no siguen la regla básica anterior: no podemos saber si x
en este ámbito se refiere a alguna variable externa o al x
-definido localmente, por ejemplo:
class X:
x = x # we want to read the global x and assign it locally
bar = x # but here we want to read the local x of the previous line
class Y:
if some_condition:
x = 42
print x # may refer to either the local x, or some global x
class Z:
for i in range(2):
print x # prints the global x the 1st time, and 42 the 2nd time
x = 42
Entonces, en los ámbitos de clase, se usa una regla diferente: donde normalmente aumentaría UnboundLocalError --- y solo en ese caso --- en cambio buscará en el módulo global. Eso es todo: no sigue la cadena de ámbitos anidados.
Por qué no? Realmente dudo que haya una mejor explicación que "por razones históricas". En términos más técnicos, podría considerar que la variable x
se define localmente en el ámbito de clase (porque está asignada a) y debe pasarse desde el ámbito principal como una variable léxicamente anidada (porque se lee). Sería posible implementarlo utilizando un bytecode diferente de LOAD_NAME
que busca en el ámbito local, y vuelve a utilizar la referencia del alcance anidado si no se encuentra.
EDITAR: gracias wilberforce por la referencia a http://bugs.python.org/issue532860 . Es posible que tengamos la oportunidad de reactivar un poco de discusión con el nuevo bytecode propuesto, si creemos que debe ser corregido después de todo (el informe de error considera la eliminación de soporte para x = x
pero se cerró por temor a romper demasiado código existente; lo que sugiero aquí sería hacer que x = x
funcione en más casos). O puedo estar perdiendo otro punto fino ...
EDIT2: parece que CPython hizo precisamente eso en el tronco actual 3.4: http://bugs.python.org/issue17853 ... ¿o no? Introdujeron el bytecode por una razón ligeramente diferente y no lo usan sistemáticamente ...
Este mensaje es un poco largo con muchos ejemplos, pero espero que nos ayude a mí y a otros a comprender mejor la historia completa de las variables y la búsqueda de atributos en Python 2.7.
Estoy usando los términos de PEP 227 ( http://www.python.org/dev/peps/pep-0227/ ) para bloques de código (como módulos, definición de clases, definiciones de funciones, etc.) y enlaces variables (tales como asignaciones, declaraciones de argumentos, declaración de clase y función, para bucles, etc.)
Estoy usando los términos variables para los nombres que se pueden llamar sin un punto, y los atributos para los nombres que deben calificarse con un nombre de objeto (como obj.x para el atributo x del objeto obj).
Hay tres ámbitos en Python para todos los bloques de código, pero las funciones:
- Local
- Global
- Incorporado
Hay cuatro bloques en Python para las funciones solamente (de acuerdo con PEP 227):
- Local
- Funciones adjuntas
- Global
- Incorporado
La regla para una variable para vincularla y encontrarla en un bloque es bastante simple:
- cualquier enlace de una variable a un objeto en un bloque hace que esta variable sea local para este bloque, a menos que la variable se declare global (en ese caso, la variable pertenece al ámbito global)
- se busca una referencia a una variable usando la regla LGB (local, global, incorporada) para todos los bloques, pero las funciones
- se busca una referencia a una variable usando la regla LEGB (local, adjuntando, global, incorporada) solo para las funciones.
Avíseme sobre ejemplos que validan esta regla y muestran muchos casos especiales. Para cada ejemplo, daré mi entendimiento. Por favor, corríjame si estoy equivocado. Para el último ejemplo, no entiendo el resultado.
Ejemplo 1:
x = "x in module"
class A():
print "A: " + x #x in module
x = "x in class A"
print locals()
class B():
print "B: " + x #x in module
x = "x in class B"
print locals()
def f(self):
print "f: " + x #x in module
self.x = "self.x in f"
print x, self.x
print locals()
>>>A.B().f()
A: x in module
{''x'': ''x in class A'', ''__module__'': ''__main__''}
B: x in module
{''x'': ''x in class B'', ''__module__'': ''__main__''}
f: x in module
x in module self.x in f
{''self'': <__main__.B instance at 0x00000000026FC9C8>}
No hay un ámbito anidado para las clases (regla LGB) y una función en una clase no puede acceder a los atributos de la clase sin utilizar un nombre calificado (self.x en este ejemplo). Esto está bien descrito en PEP227.
ejemplo 2:
z = "z in module"
def f():
z = "z in f()"
class C():
z = "z in C"
def g(self):
print z
print C.z
C().g()
f()
>>>
z in f()
z in C
Aquí las variables en funciones se buscan usando la regla LEGB, pero si una clase está en la ruta, los argumentos de la clase se omiten. Aquí nuevamente, esto es lo que PEP 227 está explicando.
ejemplo 3:
var = 0
def func():
print var
var = 1
>>> func()
Traceback (most recent call last):
File "<pyshell#102>", line 1, in <module>
func()
File "C:/Users/aa/Desktop/test2.py", line 25, in func
print var
UnboundLocalError: local variable ''var'' referenced before assignment
Esperamos con un lenguaje dinámico como python que todo se resuelva de forma dinámica. Pero este no es el caso para las funciones. Las variables locales se determinan en tiempo de compilación. PEP 227 y http://docs.python.org/2.7/reference/executionmodel.html describen este comportamiento de esta manera
"Si se produce una operación de vinculación de nombre en cualquier lugar dentro de un bloque de código, todos los usos del nombre dentro del bloque se tratan como referencias al bloque actual".
ejemplo 4:
x = "x in module"
class A():
print "A: " + x
x = "x in A"
print "A: " + x
print locals()
del x
print locals()
print "A: " + x
>>>
A: x in module
A: x in A
{''x'': ''x in A'', ''__module__'': ''__main__''}
{''__module__'': ''__main__''}
A: x in module
Pero vemos aquí que esta declaración en PEP227 "Si una operación de enlace de nombre ocurre en cualquier lugar dentro de un bloque de código, todos los usos del nombre dentro del bloque se tratan como referencias al bloque actual". es incorrecto cuando el bloque de código es una clase. Además, para las clases, parece que el enlace de nombre local no se realiza en tiempo de compilación, sino durante la ejecución utilizando el espacio de nombres de clase. En ese sentido, PEP227 y el modelo de ejecución en el documento Python es engañoso y, en algunas partes, incorrecto.
ejemplo 5:
x = ''x in module''
def f2():
x = ''x in f2''
def myfunc():
x = ''x in myfunc''
class MyClass(object):
x = x
print x
return MyClass
myfunc()
f2()
>>>
x in module
mi comprensión de este código es la siguiente. La instrucción x = x primero busca el objeto al que hace referencia la mano derecha x de la expresión. En ese caso, el objeto se busca localmente en la clase, luego siguiendo la regla LGB se busca en el alcance global, que es la cadena ''x en el módulo''. Entonces se crea un atributo local x a MyClass en el diccionario de clase y se apunta al objeto de cadena.
ejemplo 6:
Ahora aquí hay un ejemplo que no puedo explicar. Está muy cerca del ejemplo 5, solo estoy cambiando el atributo MyClass local de xa y.
x = ''x in module''
def f2():
x = ''x in f2''
def myfunc():
x = ''x in myfunc''
class MyClass(object):
y = x
print y
return MyClass
myfunc()
f2()
>>>
x in myfunc
¿Por qué en ese caso la referencia x en MyClass se busca en la función más interna?
En un mundo ideal, estarías en lo cierto y algunas de las inconsistencias que encontraste serían incorrectas. Sin embargo, CPython ha optimizado algunos escenarios, específicamente locales de función. Estas optimizaciones, junto con la forma en que el compilador y el bucle de evaluación interactúan y el precedente histórico, conducen a la confusión.
Python traduce el código a bytecodes, y luego estos son interpretados por un bucle de intérprete. El código de operación ''regular'' para acceder a un nombre es LOAD_NAME
, que busca un nombre de variable como lo haría en un diccionario. LOAD_NAME
primero buscará un nombre como local y, si eso falla, buscará un nombre global. LOAD_NAME
arroja una excepción NameError
cuando no se encuentra el nombre.
Para ámbitos anidados, buscar nombres fuera del alcance actual se implementa mediante cierres; si un nombre no está asignado pero está disponible en un ámbito anidado (no global), dichos valores se manejan como un cierre. Esto es necesario porque un ámbito principal puede contener diferentes valores para un nombre dado en diferentes momentos; dos llamadas a una función primaria pueden llevar a diferentes valores de cierre. Así que Python tiene LOAD_CLOSURE
MAKE_CLOSURE
LOAD_CLOSURE
, MAKE_CLOSURE
y LOAD_DEREF
para esa situación; los dos primeros LOAD_DEREF
se utilizan para cargar y crear un cierre para un ámbito anidado, y LOAD_DEREF
cargará el valor cerrado cuando el ámbito anidado lo necesite.
Ahora, LOAD_NAME
es relativamente lento; consultará dos diccionarios, lo que significa que primero debe hacer clic en la tecla y ejecutar algunas pruebas de igualdad (si el nombre no fue internado). Si el nombre no es local, entonces tiene que hacer esto de nuevo para un global. Para las funciones, que potencialmente se pueden llamar decenas de miles de veces, esto puede volverse tedioso rápidamente. Así que los locales tienen códigos de operación especiales. Cargando un nombre local es implementado por LOAD_FAST
, que busca las variables locales por índice en una matriz especial de nombres locales. Esto es mucho más rápido, pero requiere que el compilador primero tenga que ver si un nombre es local y no global. Para poder buscar nombres globales, se usa otro código de operación LOAD_GLOBAL
. El compilador optimiza explícitamente en este caso para generar los códigos de operación especiales. LOAD_FAST
lanzará una excepción UnboundLocalError
cuando aún no haya un valor para el nombre.
Por otro lado, los cuerpos de definición de clase, aunque se tratan como una función, no obtienen este paso de optimización. Las definiciones de clase no están destinadas a ser llamadas con tanta frecuencia; la mayoría de los módulos crean clases una vez , cuando se importan. Los ámbitos de clase tampoco cuentan cuando anidan, por lo que las reglas son más simples. Como resultado, los cuerpos de definición de clase no actúan como funciones cuando comienzas a mezclar un poco los ámbitos.
Por lo tanto, para ámbitos sin función, LOAD_NAME
y LOAD_DEREF
se utilizan para locales y globales, y para cierres, respectivamente. Para las funciones, LOAD_FAST
, LOAD_GLOBAL
y LOAD_DEREF
se utilizan en su lugar.
Tenga en cuenta que los cuerpos de clase se ejecutan tan pronto como Python ejecuta la línea de class
. Por lo tanto, en el ejemplo 1, la class B
dentro de la class A
se ejecuta tan pronto como se ejecuta la class A
, que es cuando se importa el módulo. En el ejemplo 2, C
no se ejecuta hasta que se llame a f()
, no antes.
Veamos tus ejemplos:
Has anidado una clase
AB
en una claseA
Los cuerpos de clase no forman ámbitos anidados, por lo tanto, aunque el cuerpo de la claseAB
se ejecuta cuando se ejecuta la claseA
, el compilador usaráLOAD_NAME
para buscarx
.AB().f()
es una función (vinculada a la instanciaB()
como método), por lo que usaLOAD_GLOBAL
para cargarx
. Aquí ignoraremos el acceso a los atributos, es un patrón de nombre muy bien definido.Aquí
f().Cz
está en el ámbito de clase, por lo que la funciónf().C().g()
omitirá el ámbitoC
y observará el ámbitof()
lugar, utilizandoLOAD_DEREF
.Aquí el compilador determinó que
var
era un local porque lo asigna dentro del alcance. Las funciones están optimizadas, por lo queLOAD_FAST
se usa para buscar el local y se lanza una excepción.Ahora las cosas se ponen un poco raras.
class A
se ejecuta en el alcance de clase, por lo queLOAD_NAME
se está utilizando.Ax
se eliminó del diccionario local para el alcance, por lo que el segundo acceso ax
da como resultado que se encuentre lax
global;LOAD_NAME
buscó un local primero y no lo encontró allí, volviendo a la búsqueda global.Sí, esto parece ser inconsistente con la documentación. Python-the-language y CPython-la implementación están chocando un poco aquí. Sin embargo, estás empujando los límites de lo que es posible y práctico en un lenguaje dinámico; Comprobar si
x
debería haber sido un local enLOAD_NAME
sería posible, pero lleva un tiempo de ejecución precioso para un caso de esquina que la mayoría de los desarrolladores nunca encontrarán.Ahora estás confundiendo el compilador. Usaste
x = x
en el alcance de la clase y, por lo tanto, estás configurando un local de un nombre fuera del alcance. El compilador encuentra quex
es un local aquí (lo asigna), por lo que nunca considera que también podría ser un nombre delimitado. El compilador usaLOAD_NAME
para todas las referencias ax
en este ámbito, porque este no es un cuerpo de función optimizado.Al ejecutar la definición de clase,
x = x
primero requiere que busquex
, por lo que usaLOAD_NAME
para hacerlo. No se definex
,LOAD_NAME
no encuentra un local, por lo que se encuentra lax
global . El valor resultante se almacena como local, que también se denominax
.print x
usaLOAD_NAME
nuevamente, y ahora encuentra el nuevo valor dex
local.Aquí no confundiste el compilador. Está creando un
y
local,x
no es local, por lo que el compilador lo reconoce como un nombre de ámbito de la función primariaf2().myfunc()
.x
se busca conLOAD_DEREF
desde el cierre, y se almacena eny
.
Podrías ver la confusión entre 5 y 6 como un error, aunque uno que no vale la pena arreglar en mi opinión. Ciertamente fue archivado como tal, vea el http://bugs.python.org/issue532860 en el rastreador de http://bugs.python.org/issue532860 de Python, ha estado allí por más de 10 años.
El compilador podría buscar un nombre delimitado x
incluso cuando x
también es local, para esa primera asignación en el ejemplo 5. O LOAD_NAME
podría verificar si el nombre es realmente local, y arrojar un UnboundLocalError
si no se encontró un local , a expensas de un mayor rendimiento. Si esto hubiera estado dentro del alcance de una función, LOAD_FAST
se habría utilizado para el ejemplo 5, y se UnboundLocalError
un UnboundLocalError
inmediatamente.
Sin embargo, como se muestra el error de referencia, por razones históricas, se conserva el comportamiento. Probablemente hoy haya un código que se romperá si se soluciona este error.
Para abreviar, este es un caso de esquina del alcance de Python que es un poco inconsistente, pero tiene que mantenerse para la compatibilidad con versiones anteriores (y porque no está tan claro cuál debería ser la respuesta correcta). Puede ver mucha discusión original al respecto en la lista de correo de Python cuando se estaba implementando PEP 227, y algunos en el http://bugs.python.org/issue532860 para el cual este comportamiento es la solución.
Podemos averiguar por qué hay una diferencia al usar el módulo dis
, que nos permite mirar dentro de los objetos de código para ver el bytecode en el que se ha compilado un fragmento de código. Estoy en Python 2.6, por lo que los detalles de esto pueden ser ligeramente diferentes, pero veo el mismo comportamiento, así que creo que es probablemente lo suficientemente cerca de 2.7.
El código que inicializa cada MyClass
anidado vive en un objeto de código al que puede acceder a través de los atributos de las funciones de nivel superior. (Estoy renombrando las funciones del ejemplo 5 y el ejemplo 6 a f1
y f2
respectivamente.)
El objeto de código tiene una tupla co_consts
, que contiene el objeto de código myfunc
, que a su vez tiene el código que se ejecuta cuando se crea MyClass
:
In [20]: f1.func_code.co_consts
Out[20]: (None,
''x in f2'',
<code object myfunc at 0x1773e40, file "<ipython-input-3-6d9550a9ea41>", line 4>)
In [21]: myfunc1_code = f1.func_code.co_consts[2]
In [22]: MyClass1_code = myfunc1_code.co_consts[3]
In [23]: myfunc2_code = f2.func_code.co_consts[2]
In [24]: MyClass2_code = myfunc2_code.co_consts[3]
Luego puede ver la diferencia entre ellos en bytecode usando dis.dis
:
In [25]: from dis import dis
In [26]: dis(MyClass1_code)
6 0 LOAD_NAME 0 (__name__)
3 STORE_NAME 1 (__module__)
7 6 LOAD_NAME 2 (x)
9 STORE_NAME 2 (x)
8 12 LOAD_NAME 2 (x)
15 PRINT_ITEM
16 PRINT_NEWLINE
17 LOAD_LOCALS
18 RETURN_VALUE
In [27]: dis(MyClass2_code)
6 0 LOAD_NAME 0 (__name__)
3 STORE_NAME 1 (__module__)
7 6 LOAD_DEREF 0 (x)
9 STORE_NAME 2 (y)
8 12 LOAD_NAME 2 (y)
15 PRINT_ITEM
16 PRINT_NEWLINE
17 LOAD_LOCALS
18 RETURN_VALUE
Entonces, la única diferencia es que en MyClass1
, x
se carga usando LOAD_NAME
, mientras que en MyClass2
, se carga usando LOAD_DEREF
. LOAD_DEREF
busca un nombre en un ámbito adjunto, por lo que obtiene ''x in myfunc''. LOAD_NAME
no sigue los ámbitos anidados: como no puede ver los nombres de x
enlazados en myfunc
o f1
, obtiene el enlace de nivel de módulo.
Entonces la pregunta es, ¿por qué el código de las dos versiones de MyClass
se compila en dos códigos de MyClass
diferentes? En f1
el enlace está sombreando x
en el alcance de la clase, mientras que en f2
está vinculando un nuevo nombre. Si los ámbitos MyClass
fueran funciones anidadas en lugar de clases, la línea y = x
en f2
se compilaría de la misma manera, pero x = x
en f1
sería LOAD_FAST
, esto es porque el compilador sabría que x
está vinculado a la función , por lo que debe usar LOAD_FAST
para recuperar una variable local. Esto fallaría con UnboundLocalError
cuando se llamó.
In [28]: x = ''x in module''
def f3():
x = ''x in f2''
def myfunc():
x = ''x in myfunc''
def MyFunc():
x = x
print x
return MyFunc()
myfunc()
f3()
---------------------------------------------------------------------------
Traceback (most recent call last)
<ipython-input-29-9f04105d64cc> in <module>()
9 return MyFunc()
10 myfunc()
---> 11 f3()
<ipython-input-29-9f04105d64cc> in f3()
8 print x
9 return MyFunc()
---> 10 myfunc()
11 f3()
<ipython-input-29-9f04105d64cc> in myfunc()
7 x = x
8 print x
----> 9 return MyFunc()
10 myfunc()
11 f3()
<ipython-input-29-9f04105d64cc> in MyFunc()
5 x = ''x in myfunc''
6 def MyFunc():
----> 7 x = x
8 print x
9 return MyFunc()
UnboundLocalError: local variable ''x'' referenced before assignment
Esto falla porque la función MyFunc
luego usa LOAD_FAST
:
In [31]: myfunc_code = f3.func_code.co_consts[2]
MyFunc_code = myfunc_code.co_consts[2]
In [33]: dis(MyFunc_code)
7 0 LOAD_FAST 0 (x)
3 STORE_FAST 0 (x)
8 6 LOAD_FAST 0 (x)
9 PRINT_ITEM
10 PRINT_NEWLINE
11 LOAD_CONST 0 (None)
14 RETURN_VALUE
(Por otro lado, no es una gran sorpresa que haya una diferencia en cómo el alcance interactúa con el código en el cuerpo de las clases y el código en una función. Puede decirlo porque las vinculaciones en el nivel de clase no están disponibles en los métodos: los ámbitos de los métodos no están anidados dentro del ámbito de la clase de la misma forma que las funciones anidadas. Tiene que llegar explícitamente a ellos a través de la clase, o utilizando self.
(que caerá en la clase si no hay un nivel de instancia) Unión).)