Python globali, locali e UnboundLocalError
-
03-07-2019 - |
Domanda
Mi sono imbattuto recentemente in questo caso di UnboundLocalError
, il che sembra strano:
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()
Che 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
è chiaramente associato in globals
e verrà associato in locals
nella seguente dichiarazione. Qualcuno può offrire una spiegazione del perché non è felice di risolvere pprint
al binding in globals
qui?
Modifica: Grazie alle buone risposte posso chiarire la mia domanda con una terminologia pertinente:
Al momento della compilazione l'identificatore pprint
è contrassegnato come locale al frame. Il modello esecutivo non ha distinzioni dove all'interno del frame è associato l'identificatore locale? Può dire "fare riferimento all'associazione globale fino a quando questa istruzione bytecode, a quel punto è stata rimbalzata ad un'associazione locale," o il modello di esecuzione non tiene conto di questo?
Soluzione
Sembra che Python veda il dalla riga import pprint
e contrassegni pprint
come un nome locale in main ()
prima di eseguendo qualsiasi codice. Poiché Python pensa che pprint dovrebbe essere una variabile locale, facendo riferimento a pprint.pprint ()
prima di " assegnando " con l'istruzione from..import
, genera quell'errore.
È tutto il più sensato possibile.
La morale, ovviamente, è sempre mettere quelle import
in cima all'ambito.
Altri suggerimenti
Dov'è la sorpresa? Qualsiasi variabile globale in un ambito che si riassegna all'interno di tale ambito è contrassegnata come locale per tale ambito dal compilatore.
Se le importazioni fossero gestite in modo diverso, quello sarebbe sorprendente imho.
Potrebbe essere utile non nominare i moduli dopo i simboli utilizzati al loro interno, o viceversa, tuttavia.
Beh, è ??stato abbastanza interessante per me sperimentare un po 'e ho letto http: / /docs.python.org/reference/executionmodel.html
Poi ho armeggiato un po 'con il tuo codice qua e là, questo è quello che ho potuto trovare:
codice:
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()
uscita:
<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'
Nel metodo due ()
dall'importazione pprint ma non sovrascrive il nome
pprint
in globals
, poiché la parola chiave globale
non è non utilizzata nell'ambito di due ()
.
Nel metodo tre ()
poiché non esiste una dichiarazione del nome pprint
nell'ambito locale, viene impostato automaticamente il nome globale pprint
che è un modulo
Considerando che in main ()
, all'inizio viene usata la parola chiave global
quindi tutti i riferimenti a pprint
in l'ambito del metodo main ()
farà riferimento al global
name pprint
. Che come possiamo vedere all'inizio è un modulo ed è scavalcato nello spazio dei nomi global
con un metodo come facciamo il from pprint import pprint
Anche se questo potrebbe non rispondere alla domanda in quanto tale, ma penso che sia un fatto interessante, penso.
=====================
Modifica Un'altra cosa interessante.
Se hai un modulo dire:
mod1
from datetime import datetime
def foo():
print "bar"
e un altro metodo dice:
mod2
import datetime
from mod1 import *
if __name__ == '__main__':
print datetime.datetime.now()
che a prima vista è apparentemente corretto poiché hai importato il modulo datetime
in mod2
.
ora se si tenta di eseguire mod2 come script, verrà generato un errore:
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'
perché la seconda importazione dall'importazione mod2 *
ha sostituito il nome datetime
nello spazio dei nomi, quindi il primo import datetime
non è più valido .
Morale: quindi l'ordine delle importazioni, la natura delle importazioni (da x import *) e la consapevolezza delle importazioni all'interno dei moduli importati - importa .
A questa domanda è stata data risposta diverse settimane fa, ma penso di poter chiarire un po 'le risposte. Innanzitutto alcuni fatti.
1: in Python,
import foo
è quasi identico a
foo = __import__("foo", globals(), locals(), [], -1)
2: quando si esegue il codice in una funzione, se Python rileva una variabile che non è stata ancora definita nella funzione, appare nell'ambito globale.
3: Python ha un'ottimizzazione che utilizza per le funzioni chiamate "locali". Quando Python tokenizza una funzione, tiene traccia di tutte le variabili assegnate a. Assegna a ciascuna di queste variabili un numero da un intero locale monotonicamente crescente. Quando Python esegue la funzione, crea un array con tutti gli slot quante sono le variabili locali e assegna a ciascuno slot un valore speciale che significa che "non è stato ancora assegnato a", ed è lì che sono memorizzati i valori per quelle variabili . Se fai riferimento a un locale a cui non è stato ancora assegnato, Python vede quel valore speciale e genera un'eccezione UnboundLocalValue.
Lo stage è ora impostato. Il tuo " da pprint import pprint " è davvero una forma di incarico. Quindi Python crea una variabile locale chiamata " pprint " che occlude la variabile globale. Quindi, quando fai riferimento a " pprint.pprint " nella funzione, premi il valore speciale e Python genera l'eccezione. Se non avessi quell'istruzione import nella funzione, Python userebbe la normale risoluzione look-in-locals-first-poi-look-in-globals e troverebbe il modulo pprint in globals.
Per chiarire questo, puoi utilizzare il " global " parola chiave. Ovviamente ormai hai già superato il tuo problema e non so se hai davvero bisogno di " global " o se fosse richiesto un altro approccio.