modulenotfounderror - python nonlocal
Python globales, lugareƱos y UnboundLocalError (4)
¿Dónde está la sorpresa? El compilador marca localmente en ese ámbito cualquier variable global a un ámbito que reasigne dentro de dicho ámbito.
Si las importaciones se manejaran de manera diferente, eso sería sorprendente.
Puede hacer un caso para no nombrar módulos después de los símbolos utilizados en el mismo, o viceversa.
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()
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 ligado en globals
, y estará ligado en locals
en la siguiente declaración. ¿Alguien puede ofrecer una explicación de por qué no es feliz resolver pprint
para el enlace en globals
aquí?
Editar: Gracias a las buenas respuestas, puedo aclarar mi pregunta con la terminología pertinente:
En tiempo de compilación, el identificador pprint
se marca como local para el marco. ¿El modelo de ejecución no tiene distinción donde dentro del marco está vinculado el identificador local? ¿Puede decir "se refiere a la vinculación global hasta esta instrucción de código de bytes, en cuyo punto se ha recuperado a un enlace local" o el modelo de ejecución no cuenta para esto?
Bueno, eso fue lo suficientemente interesante como para experimentar un poco y lo leí a través de http://docs.python.org/reference/executionmodel.html
Luego hice algunos retoques con su 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()
from pprint import pprint
pero no reemplaza el nombre pprint
en globals
, ya que la palabra clave global
no se usa en el ámbito de two()
.
En el método three()
ya que no hay una declaración del nombre de la pprint
en el ámbito local, se toma por defecto el nombre global pprint
que es un módulo
Mientras que en main()
, al principio se utiliza la palabra clave global
por lo que todas las referencias a pprint
en el ámbito del método main()
se referirán al nombre global
pprint
. Lo cual, como podemos ver, es un módulo al principio y se anula en el namespace
global
con un método, como lo hacemos from pprint import pprint
Aunque puede que no responda 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 dice:
mod2
import datetime
from mod1 import *
if __name__ == ''__main__'':
print datetime.datetime.now()
que a primera vista es aparentemente correcto ya que ha importado el módulo datetime
en mod2
.
ahora si intenta ejecutar mod2 como un script lanzará 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 la segunda importación from mod2 import *
ha from mod2 import *
el nombre datetime
en el espacio de nombres, por lo tanto, la primera import datetime
ya no es válida.
Moral: Por lo tanto, importa 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.
Parece que Python ve la línea from pprint import pprint
y marca pprint
como un nombre local para main()
antes de ejecutar cualquier código. Como Python cree que la pprint debe ser una variable local, haciendo referencia a ella con pprint.pprint()
antes de "asignarla" con la declaración from..import
, arroja ese error.
Eso es todo el sentido que puedo tener.
La moraleja, por supuesto, es colocar siempre esas declaraciones de import
en la parte superior del alcance.
Esta pregunta fue respondida 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: cuando se ejecuta código en una función, si Python encuentra una variable que todavía no se ha definido en la función, se ve en el alcance global.
3: Python tiene una optimización que usa para funciones llamadas "locales". Cuando Python tokenizes 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 "no se ha asignado todavía", y ahí es donde se almacenan los valores para esas variables. Si hace referencia a un local que aún no ha sido asignado, Python ve ese valor especial y arroja una excepción de UnboundLocalValue.
El escenario ahora está establecido. Su "de pprint import pprint" es realmente una forma de asignación. Entonces Python crea una variable local llamada "pprint" que ocluye la variable global. Luego, cuando te refieres a "pprint.pprint" en la función, tocas el valor especial y Python lanza la excepción. Si no tuvieras esa declaración de importación en la función, Python usaría la resolución normal look-in-locals-first-then-look-in-globals y encontraría el módulo pprint en globales.
Para desambiguar esto, puede usar la palabra clave "global". Por supuesto, ya ha superado su problema y no sé si realmente necesitaba "global" o si se requería algún otro enfoque.