Globos de Python, locales y UnboundLocalError
-
03-07-2019 - |
Pregunta
Me encontré con este caso de UnboundLocalError
recientemente, lo que parece extraño:
import pprint
def main():
if 'pprint' in globals(): print 'pprint is in globals()'
pprint.pprint('Spam')
from pprint import pprint
pprint('Eggs')
if __name__ == '__main__': main()
Lo que produce:
pprint is in globals()
Traceback (most recent call last):
File "weird.py", line 9, in <module>
if __name__ == '__main__': main()
File "weird.py", line 5, in main
pprint.pprint('Spam')
UnboundLocalError: local variable 'pprint' referenced before assignment
pprint
está claramente vinculado en globals
, y se enlazará en locals
en la siguiente declaración. ¿Alguien puede ofrecer una explicación de por qué no está satisfecho resolver pprint
al enlace en globals
aquí?
Editar: Gracias a las buenas respuestas, puedo aclarar mi pregunta con la terminología pertinente:
En el momento de la compilación, el identificador pprint
está marcado como local al marco. ¿El modelo de ejecución no tiene distinción donde dentro del marco al que está enlazado el identificador local? ¿Puede decir, "referirse al enlace global hasta esta instrucción de bytecode, en cuyo punto se ha rebotado a un enlace local"? ¿O el modelo de ejecución no tiene en cuenta esto?
Solución
Parece que Python ve la línea from pprint import pprint
y marca pprint
como un nombre local de main ()
antes de ejecutando cualquier código. Como Python cree que pprint debería ser una variable local, debe hacer referencia a ella con pprint.pprint ()
antes de " asignar " con la declaración from..import
, arroja ese error.
Eso es todo el sentido que puedo hacer de eso.
La moraleja, por supuesto, es poner siempre esas declaraciones import
en la parte superior del alcance.
Otros consejos
¿Dónde está la sorpresa? Cualquier variable global para un ámbito que reasigne dentro de ese ámbito está marcada localmente por el compilador.
Si las importaciones se manejaran de manera diferente, eso sería sorprendente en este caso.
Sin embargo, puede ser un caso para no nombrar módulos después de los símbolos utilizados en ellos, o viceversa.
Bueno, eso fue lo suficientemente interesante para que experimentara un poco y leí http: / /docs.python.org/reference/executionmodel.html
Luego hice algunos retoques con tu código aquí y allá, esto es lo que pude encontrar:
código:
import pprint
def two():
from pprint import pprint
print globals()['pprint']
pprint('Eggs')
print globals()['pprint']
def main():
if 'pprint' in globals():
print 'pprint is in globals()'
global pprint
print globals()['pprint']
pprint.pprint('Spam')
from pprint import pprint
print globals()['pprint']
pprint('Eggs')
def three():
print globals()['pprint']
pprint.pprint('Spam')
if __name__ == '__main__':
two()
print('\n')
three()
print('\n')
main()
salida:
<module 'pprint' from '/usr/lib/python2.5/pprint.pyc'>
'Eggs'
<module 'pprint' from '/usr/lib/python2.5/pprint.pyc'>
<module 'pprint' from '/usr/lib/python2.5/pprint.pyc'>
'Spam'
pprint is in globals()
<module 'pprint' from '/usr/lib/python2.5/pprint.pyc'>
'Spam'
<function pprint at 0xb7d596f4>
'Eggs'
En el método two ()
de pprint import pprint
pero no reemplaza el nombre pprint
en globals
, ya que la palabra clave global
no se utiliza en el ámbito de two ()
.
En el método three ()
, ya que no hay una declaración del nombre de pprint
en el ámbito local, su valor predeterminado es el nombre global pprint
, que es un módulo
Mientras que en main ()
, al principio se usa la palabra clave global
, por lo que todas las referencias a pprint
en El alcance del método main ()
se referirá al global
nombre pprint
. Lo que como podemos ver es un módulo al principio y está anulado en el global
namespace
con un método como lo hacemos con el de pprint import pprint
Aunque esto puede no estar respondiendo a la pregunta como tal, pero creo que es un hecho interesante.
=====================
Editar Otra cosa interesante.
Si tiene un módulo, diga:
mod1
from datetime import datetime
def foo():
print "bar"
y otro método dicen:
mod2
import datetime
from mod1 import *
if __name__ == '__main__':
print datetime.datetime.now()
que a primera vista parece ser correcto ya que ha importado el módulo datetime
en mod2
.
ahora, si intenta ejecutar mod2 como script, se generará un error:
Traceback (most recent call last):
File "mod2.py", line 5, in <module>
print datetime.datetime.now()
AttributeError: type object 'datetime.datetime' has no attribute 'datetime'
porque el segundo importado de mod2 import *
ha invalidado el nombre datetime
en el espacio de nombres, por lo tanto, el primer import datetime
ya no es válido .
Moraleja: por lo tanto, el orden de las importaciones, la naturaleza de las importaciones (de x import *) y el conocimiento de las importaciones dentro de los módulos importados - importa .
Esta pregunta se respondió hace varias semanas, pero creo que puedo aclarar un poco las respuestas. Primero algunos hechos.
1: En Python,
import foo
es casi exactamente lo mismo que
foo = __import__("foo", globals(), locals(), [], -1)
2: al ejecutar código en una función, si Python encuentra una variable que aún no se ha definido en la función, busca en el ámbito global.
3: Python tiene una optimización que utiliza para las funciones llamadas " locals " ;. Cuando Python tokeniza una función, realiza un seguimiento de todas las variables que asigna. Asigna a cada una de estas variables un número de un entero local monótonamente creciente. Cuando Python ejecuta la función, crea una matriz con tantas ranuras como variables locales, y asigna a cada ranura un valor especial que significa que "aún no se ha asignado", y ahí es donde se almacenan los valores de esas variables. . Si hace referencia a un local que aún no se ha asignado, Python ve ese valor especial y lanza una excepción UnboundLocalValue.
El escenario está listo. Tu " desde pprint import pprint " Es realmente una forma de asignación. Así que Python crea una variable local llamada " pprint " que ocluye la variable global. Luego, cuando te refieres a " pprint.pprint " en la función, pulsas el valor especial y Python lanza la excepción. Si no tuviera esa declaración de importación en la función, Python usaría la resolución normal look-in-locals-first-look-in-globals y buscaría el módulo de impresión en globals.
Para desambiguar esto, puede usar " global " palabra clave. Por supuesto, ya ha superado su problema y no sé si realmente necesitaba " global " o si se requiere algún otro enfoque.