Entgegen der landläufigen Meinung ist Python keine "interpretierte" Sprache in irgendeinem sinnvollen Sinne. Solche Sprachen sind heutzutage verschwindend selten. Die Referenzimplementierung von Python kompiliert Python-Code auf die gleiche Weise wie Java oder C#: Er wird in Opcodes ("Bytecode") für ein virtuelle Maschine die dann emuliert wird. Andere Implementierungen müssen den Code ebenfalls kompilieren, ansonsten, eval
y exec
konnte nicht ordnungsgemäß ein Objekt zurückgeben, und SyntaxError
s konnten nicht erkannt werden, ohne dass der Code tatsächlich ausgeführt wurde.
Während der Kompilierung (ob in der Referenzimplementierung oder nicht) wird Python folgt einfachen Regeln für Entscheidungen über den Geltungsbereich von Variablen in einer Funktion:
-
Enthält die Funktion eine global
o nonlocal
Deklaration für einen Namen, so wird dieser Name so behandelt, als beziehe er sich auf den globalen Bereich bzw. den ersten umschließenden Bereich, der den Namen enthält.
-
Andernfalls, wenn es irgendetwas enthält Syntax für die Änderung der Bindung (entweder Zuweisung oder Löschung) des Namens, auch wenn der Code die Bindung zur Laufzeit nicht wirklich ändern würde lautet der Name . .
-
Andernfalls verweist er entweder auf den ersten umschließenden Bereich, der den Namen enthält, oder auf den globalen Bereich. (Der Builtin-Bereich wird als Fallback zur Laufzeit geprüft, wenn ein Name im globalen Bereich gesucht wird; die Zuweisung zu einem Namen, der im Builtin-Bereich liegt, erfolgt im globalen Bereich).
Wichtig ist dabei, dass der Geltungsbereich aufgelöst wird zur Kompilierzeit . Der generierte Bytecode gibt direkt an, wo er zu suchen ist. In CPython 3.8 zum Beispiel gibt es separate Opcodes LOAD_CONST
(zur Kompilierzeit bekannte Konstanten), LOAD_FAST
(Einheimische), LOAD_DEREF
(Gerät nonlocal
Lookup durch Suche in einer Closure, die als Tupel von "Cell"-Objekten implementiert ist), LOAD_CLOSURE
(Suche nach einer lokalen Variablen im Closure-Objekt, das für eine verschachtelte Funktion erstellt wurde), und LOAD_GLOBAL
(entweder im globalen Namespace oder im Builtin-Namespace nachschlagen).
Für diese Namen gibt es keinen "Standardwert". Wenn sie nicht zugewiesen wurden, bevor sie nachgeschlagen werden, wird ein NameError
auftritt. Insbesondere für lokale Suchvorgänge, UnboundLocalError
auftritt; dies ist ein Subtyp von NameError
.
Hier gibt es einige wichtige Überlegungen, wobei zu beachten ist, dass die Syntaxregel zur Kompilierzeit implementiert wird, mit keine statische Analyse :
-
Es spielt keine Rolle wenn der Code nie erreicht werden konnte:
y = 1
def x():
return y # local!
if False:
y = 0
-
Es spielt keine Rolle wenn die Zuweisung zu einer In-Place-Änderung optimiert würde (z. B. Erweiterung einer Liste) - konzeptionell wird der Wert immer noch zugewiesen, und dies spiegelt sich im Bytecode in der Referenzimplementierung als eine nutzlose Neuzuweisung des Namens an dasselbe Objekt wider:
y = []
def x():
y += [1] # local, even though it would modify `y` in-place with `global`
-
Es ist jedoch tut wenn wir stattdessen eine indizierte/Slice-Zuweisung vornehmen. (Dies wird zur Kompilierzeit in einen anderen Opcode umgewandelt, der wiederum die __getitem__
.)
y = [0]
def x():
print(y) # global now! No error occurs.
y[0] = 1
-
Es gibt auch andere Formen der Zuordnung, z. B.:
y = 1
def x():
return y # local!
for y in []:
pass
-
Die Löschung ist auch eine Änderung der Namensbindung, z.B.:
y = 1
def x():
return y # local!
del y
Der interessierte Leser, der die Referenzimplementierung verwendet, wird ermutigt, jedes dieser Beispiele mit Hilfe der dis
Modul der Standardbibliothek.
Das Problem funktioniert auf die gleiche Weise, mutatis mutandis für beide global
y nonlocal
Schlüsselwörter. (Python 2.x hat nicht nonlocal
.) In jedem Fall ist das Schlüsselwort notwendig, um der Variablen aus dem äußeren Bereich zuzuweisen, ist aber pas notwendig für einfach nachschlagen noch zu mutieren das nachgeschlagene Objekt. (Nochmals: +=
auf einer Liste mutiert die Liste, aber dann auch neu zugewiesen den Namen in dieselbe Liste).
0 Stimmen
Ist damit Ihre Frage beantwortet? Ich verstehe nicht, warum UnboundLocalError auftritt (Schließung)
1 Stimmen
Gleicher Fehler, aber andere Ursache: "UnboundLocalError: lokale Variable vor Zuweisung referenziert" nach einer if-Anweisung