Der folgende Code funktioniert wie erwartet in Python 2.5 und 3.0:
a, b, c = (1, 2, 3)
print(a, b, c)
def test():
print(a)
print(b)
print(c) # (A)
#c+=1 # (B)
test()
Wenn ich jedoch Zeile (B) auskommentiere, erhalte ich eine UnboundLocalError: 'c' not assigned
Zeile (A) . Die Werte von a
und b
werden korrekt gedruckt. Das hat mich aus zwei Gründen völlig verblüfft:
Warum wird in Zeile (A) aufgrund einer späteren Anweisung in Zeile (B) ein Laufzeitfehler ausgegeben ?
Warum sind Variablen
a
undb
wie erwartet gedruckt, währendc
ein Fehler auslöst?
Die einzige Erklärung, die ich finden kann, ist, dass durch die Zuweisung eine lokale Variable c
erstellt wird c+=1
, die Vorrang vor der "globalen" Variablen hat, c
noch bevor die lokale Variable erstellt wird. Natürlich ist es nicht sinnvoll, dass eine Variable den Bereich "stiehlt", bevor er existiert.
Könnte jemand bitte dieses Verhalten erklären?
global
odernonlocal
erzwingen eine globale oder nicht lokale Zuweisung)Python ist insofern etwas seltsam, als es alles in einem Wörterbuch für die verschiedenen Bereiche enthält. Das Original a, b, c befindet sich im obersten Bereich und damit in diesem obersten Wörterbuch. Die Funktion hat ein eigenes Wörterbuch. Wenn Sie die Anweisungen
print(a)
und erreichenprint(b)
, enthält das Wörterbuch nichts mit diesem Namen. Python schlägt die Liste nach und findet sie im globalen Wörterbuch.Jetzt kommen wir zu
c+=1
, was natürlich gleichbedeutend ist mitc=c+1
. Wenn Python diese Zeile durchsucht, heißt es: "Aha, es gibt eine Variable mit dem Namen c. Ich werde sie in mein lokales Bereichswörterbuch aufnehmen." Wenn es dann auf der rechten Seite der Zuweisung nach einem Wert für c für c sucht, findet es seine lokale Variable mit dem Namen c , die noch keinen Wert hat, und löst so den Fehler aus.Die
global c
oben erwähnte Anweisung teilt dem Parser lediglich mit, dass er denc
aus dem globalen Bereich verwendet und daher keinen neuen benötigt.Der Grund, warum es ein Problem in der Zeile gibt, ist, dass es effektiv nach den Namen sucht, bevor es versucht, Code zu generieren, und daher in gewissem Sinne nicht glaubt, dass es diese Zeile noch wirklich tut. Ich würde behaupten, dass dies ein Usability-Fehler ist, aber es ist im Allgemeinen eine gute Praxis, einfach zu lernen, die Nachrichten eines Compilers nicht zu ernst zu nehmen.
Wenn es ein Trost ist, habe ich wahrscheinlich einen Tag damit verbracht, mit demselben Thema zu graben und zu experimentieren, bevor ich etwas fand, das Guido über die Wörterbücher geschrieben hatte, die alles erklärten.
Update, siehe Kommentare:
Der Code wird nicht zweimal gescannt, aber der Code wird in zwei Phasen gescannt: Lexen und Parsen.
Überlegen Sie, wie die Analyse dieser Codezeile funktioniert. Der Lexer liest den Quelltext und zerlegt ihn in Lexeme, die "kleinsten Komponenten" der Grammatik. Also, wenn es die Linie trifft
es zerlegt es in so etwas wie
Der Parser möchte dies schließlich in einen Analysebaum umwandeln und ausführen. Da es sich jedoch um eine Zuweisung handelt, sucht er zuvor im lokalen Wörterbuch nach dem Namen c, sieht ihn nicht und fügt ihn in das Wörterbuch ein und markiert ihn es als nicht initialisiert. In einer vollständig kompilierten Sprache würde es einfach in die Symboltabelle gehen und auf die Analyse warten, aber da es nicht den Luxus eines zweiten Durchgangs hat, macht der Lexer ein wenig zusätzliche Arbeit, um das Leben später einfacher zu machen. Nur dann sieht es den OPERATOR, sieht, dass die Regeln sagen "wenn Sie einen Operator haben + = die linke Seite muss initialisiert worden sein" und sagen "whoops!"
Der Punkt hier ist, dass es noch nicht wirklich mit dem Parsen der Linie begonnen hat . Dies alles geschieht in gewisser Weise vor der eigentlichen Analyse, sodass der Zeilenzähler nicht zur nächsten Zeile vorgerückt ist. Wenn es also den Fehler signalisiert, denkt es immer noch, dass es in der vorherigen Zeile steht.
Wie gesagt, man könnte argumentieren, dass es sich um einen Usability-Fehler handelt, aber es ist tatsächlich eine ziemlich häufige Sache. Einige Compiler sind ehrlicher und sagen "Fehler in oder um Zeile XXX", aber dieser nicht.
quelle
dict
, sondern intern nur als Array behandelt (locals()
füllt ein,dict
um zurückzugeben, aber Änderungen daran erstellen keine neuenlocals
). In der Analysephase wird jede Zuordnung zu einem lokalen Objekt gefunden, von Name zu Position in diesem Array konvertiert und diese Position verwendet, wenn auf den Namen verwiesen wird. Beim Aufrufen der Funktion werden Nichtargument-Lokale mit einem Platzhalter initialisiert. DiesUnboundLocalError
geschieht, wenn eine Variable gelesen wird und der zugehörige Index weiterhin den Platzhalterwert hat.Ein Blick auf die Demontage kann verdeutlichen, was passiert:
Wie Sie sehen können, ist der Bytecode für den Zugriff auf a
LOAD_FAST
und für b ,LOAD_GLOBAL
. Dies liegt daran, dass der Compiler festgestellt hat, dass a innerhalb der Funktion zugewiesen ist, und es als lokale Variable klassifiziert hat. Der Zugriffsmechanismus für Einheimische unterscheidet sich grundlegend für Globals - ihnen wird statisch ein Offset in der Variablentabelle des Frames zugewiesen, was bedeutet, dass die Suche ein schneller Index ist und nicht die teurere Diktatsuche wie für Globals. Aus diesem Grund liest Python dieprint a
Zeile als "Wert der lokalen Variablen 'a' in Steckplatz 0 abrufen und drucken" und löst eine Ausnahme aus, wenn festgestellt wird, dass diese Variable noch nicht initialisiert ist.quelle
Python hat ein ziemlich interessantes Verhalten, wenn Sie die traditionelle globale Variablensemantik ausprobieren. Ich erinnere mich nicht an die Details, aber Sie können den Wert einer Variablen, die im 'globalen' Bereich deklariert ist, gut lesen, aber wenn Sie ihn ändern möchten, müssen Sie das
global
Schlüsselwort verwenden. Versuchen Sietest()
, dies zu ändern :Der Grund, warum Sie diesen Fehler erhalten, ist, dass Sie in dieser Funktion auch eine neue Variable mit demselben Namen wie eine 'globale' deklarieren können, die vollständig getrennt wäre. Der Interpreter glaubt, dass Sie versuchen, eine neue Variable in diesem Bereich aufzurufen
c
und alles in einer Operation zu ändern, was in Python nicht zulässig ist, da diese neuec
nicht initialisiert wurde.quelle
Das beste Beispiel, das es deutlich macht, ist:
Beim Aufruf
foo()
wird dies ebenfalls ausgelöst,UnboundLocalError
obwohl wir niemals die Zeile erreichenbar=0
werden. Daher sollte niemals eine logisch lokale Variable erstellt werden.Das Geheimnis liegt in " Python ist eine interpretierte Sprache " und die Deklaration der Funktion
foo
wird als einzelne Anweisung (dh als zusammengesetzte Anweisung) interpretiert. Sie interpretiert sie nur dumm und erstellt lokale und globale Bereiche. Wirdbar
also vor der Ausführung im lokalen Bereich erkannt.Für weitere Beispiele wie diese lesen diesen Beitrag: http://blog.amir.rachum.com/blog/2013/07/09/python-common-newbie-mistakes-part-2/
Dieser Beitrag enthält eine vollständige Beschreibung und Analyse des Python-Scoping von Variablen:
quelle
Hier sind zwei Links, die helfen können
1: docs.python.org/3.1/faq/programming.html?highlight=nonlocal#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value
2: docs.python.org/3.1/faq/programming.html?highlight=nonlocal#how-do-i-write-a-function-with-output-parameters-call-by-reference
Link eins beschreibt den Fehler UnboundLocalError. Link zwei kann beim Umschreiben Ihrer Testfunktion helfen. Basierend auf Link zwei könnte das ursprüngliche Problem wie folgt umgeschrieben werden:
quelle
Dies ist keine direkte Antwort auf Ihre Frage, aber sie ist eng miteinander verbunden, da es sich um ein weiteres Problem handelt, das durch die Beziehung zwischen erweiterter Zuweisung und Funktionsumfang verursacht wird.
In den meisten Fällen neigen Sie dazu, Augmented Assignment (
a += b
) als genau gleichwertig mit Simple Assignment (a = a + b
) zu betrachten. In einem Eckfall kann es jedoch zu Problemen kommen. Lassen Sie mich erklären:Die Art und Weise, wie die einfache Zuweisung von Python funktioniert, bedeutet, dass, wenn Python
a
an eine Funktion übergeben wird (z. B.func(a)
beachten Sie, dass Python immer als Referenz übergeben wird),a = a + b
die übergebene Funktion nicht geänderta
wird. Stattdessen wird lediglich der lokale Zeiger auf geänderta
.Wenn Sie jedoch verwenden
a += b
, wird es manchmal wie folgt implementiert:oder manchmal (wenn die Methode existiert) als:
Im ersten Fall (solange
a
nicht als global deklariert) treten keine Nebenwirkungen außerhalb des lokalen Bereichs auf, da die Zuweisung ana
nur eine Zeigeraktualisierung ist.Im zweiten Fall
a
ändert sich tatsächlich selbst, sodass alle Verweise aufa
auf die geänderte Version verweisen. Dies wird durch den folgenden Code demonstriert:Der Trick besteht also darin, eine erweiterte Zuweisung von Funktionsargumenten zu vermeiden (ich versuche, sie nur für lokale / Schleifenvariablen zu verwenden). Verwenden Sie eine einfache Zuordnung, und Sie sind vor mehrdeutigem Verhalten geschützt.
quelle
Der Python-Interpreter liest eine Funktion als vollständige Einheit. Ich stelle es mir vor, es in zwei Durchgängen zu lesen, einmal, um seinen Abschluss (die lokalen Variablen) zu erfassen, und dann wieder, um es in Bytecode umzuwandeln.
Wie Sie sicher bereits wussten, ist jeder Name, der links von einem '=' verwendet wird, implizit eine lokale Variable. Mehr als einmal wurde ich durch das Ändern eines Variablenzugriffs auf ein + = überrascht, und es ist plötzlich eine andere Variable.
Ich wollte auch darauf hinweisen, dass dies nicht wirklich etwas mit dem globalen Geltungsbereich zu tun hat. Sie erhalten das gleiche Verhalten mit verschachtelten Funktionen.
quelle
c+=1
Zuweisenc
, Python geht davon aus, dass zugewiesene Variablen lokal sind, aber in diesem Fall wurde es nicht lokal deklariert.Verwenden Sie entweder die Schlüsselwörter
global
odernonlocal
.nonlocal
funktioniert nur in Python 3. Wenn Sie also Python 2 verwenden und Ihre Variable nicht global machen möchten, können Sie ein veränderbares Objekt verwenden:quelle
Der beste Weg, um eine Klassenvariable zu erreichen, ist der direkte Zugriff über den Klassennamen
quelle
In Python haben wir eine ähnliche Deklaration für alle Arten von Variablen, lokale, Klassenvariablen und globale Variablen. Wenn Sie eine globale Variable von der Methode referenzieren, denkt Python, dass Sie tatsächlich eine Variable von der Methode selbst referenzieren, die noch nicht definiert ist, und wirft daher einen Fehler aus. Um auf eine globale Variable zu verweisen, müssen wir globals () ['variableName'] verwenden.
Verwenden Sie in Ihrem Fall globals () ['a], globals () [' b '] und globals () [' c '] anstelle von a, b bzw. c.
quelle
Das gleiche Problem stört mich. Verwenden
nonlocal
undglobal
kann das Problem lösen.Die für die Verwendung von erforderliche Aufmerksamkeit
nonlocal
funktioniert jedoch für verschachtelte Funktionen. In einer Modulebene funktioniert dies jedoch nicht. Siehe Beispiele hier.quelle