305 Stimmen

UnboundLocalError bei lokaler Variable, wenn diese nach der ersten Verwendung neu zugewiesen wird

Der folgende Code funktioniert wie erwartet sowohl in Python 2.5 als auch in 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 die Zeile (B) erhalte ich eine UnboundLocalError: 'c' not assigned auf der Linie (A) . Die Werte von a et b korrekt gedruckt werden. Das hat mich aus zwei Gründen völlig verwirrt:

  1. Warum wird ein Laufzeitfehler in der Zeile (A) wegen einer späteren Aussage in der Zeile (B) ?

  2. Warum sind Variablen a et b wie erwartet gedruckt, während c einen Fehler auslöst?

Die einzige Erklärung, die mir einfällt, ist, dass ein . variabel c wird durch den Auftrag c+=1 die Vorrang vor der "globalen" Variable hat c noch bevor die lokale Variable erstellt wird. Natürlich ist es nicht sinnvoll, dass eine Variable den Geltungsbereich "stiehlt", bevor sie existiert.

Könnte mir jemand dieses Verhalten erklären?

0 Stimmen

1 Stimmen

316voto

recursive Punkte 80517

Python behandelt Variablen in Funktionen unterschiedlich, je nachdem, ob Sie ihnen Werte innerhalb oder außerhalb der Funktion zuweisen. Wenn eine Variable innerhalb einer Funktion zugewiesen wird, wird sie standardmäßig als lokale Variable behandelt. Wenn Sie also die Zeile auskommentieren, versuchen Sie, die lokale Variable zu referenzieren c bevor ihm ein Wert zugewiesen wurde.

Wenn Sie wollen, dass die Variable c um auf die globale c = 3 vor der Funktion zugewiesen, setzen Sie

global c

als erste Zeile der Funktion.

Was Python 3 betrifft, so gibt es jetzt

nonlocal c

die Sie verwenden können, um auf den nächstgelegenen umschließenden Funktionsbereich zu verweisen, der eine c variabel.

94voto

Charlie Martin Punkte 106684

Python ist ein wenig seltsam, dass es alles in einem Wörterbuch für die verschiedenen Bereiche hält. Die ursprünglichen a,b,c befinden sich im obersten Bereich und damit in diesem obersten Wörterbuch. Die Funktion hat ihr eigenes Wörterbuch. Wenn Sie den print(a) y print(b) Anweisungen gibt es nichts mit diesem Namen im Wörterbuch, also schaut Python in der Liste nach und findet sie im globalen Wörterbuch.

Jetzt kommen wir zu c+=1 , was natürlich gleichbedeutend ist mit c=c+1 . Wenn Python diese Zeile durchsucht, sagt es: "Aha, es gibt eine Variable mit dem Namen c, ich werde sie in mein lokales Wörterbuch aufnehmen." Wenn es dann nach einem Wert für c für das c auf der rechten Seite der Zuweisung sucht, findet es seinen lokale Variable namens c die noch keinen Wert hat und daher den Fehler auslöst.

Die Erklärung global c sagt dem Parser einfach, dass er die c aus dem globalen Bereich und benötigt daher keinen neuen.

Der Grund, warum es sagt, es gibt ein Problem in der Zeile, die es tut, ist, weil es effektiv für die Namen suchen, bevor es versucht, Code zu generieren, und so in gewissem Sinne nicht denken, es ist wirklich tun, dass Zeile noch. Ich würde argumentieren, dass dies ein Fehler in der Benutzerfreundlichkeit ist, aber es ist im Allgemeinen eine gute Praxis, einfach zu lernen, die Meldungen eines Compilers nicht zu beachten zu ernsthaft.

Falls es Sie tröstet, ich habe wahrscheinlich einen ganzen Tag mit demselben Thema verbracht, bevor ich etwas fand, das Guido über die Wörterbücher, die alles erklären, geschrieben hatte.

Update, siehe Kommentare:

Der Code wird nicht zweimal gescannt, sondern in zwei Phasen: Lexing und Parsing.

Betrachten Sie, wie das Parsen dieser Codezeile funktioniert. Der Lexer liest den Quelltext und zerlegt ihn in Lexeme, die "kleinsten Bestandteile" der Grammatik. Wenn er also auf die Zeile

c+=1

zerfällt es in etwas wie

SYMBOL(c) OPERATOR(+=) DIGIT(1)

Der Parser will daraus schließlich einen Parse-Baum machen und ihn ausführen, aber da es sich um eine Zuweisung handelt, sucht er vorher nach dem Namen c im lokalen Wörterbuch, findet ihn nicht und fügt ihn in das Wörterbuch ein und markiert ihn als nicht initialisiert. In einer vollständig kompilierten Sprache würde er einfach in die Symboltabelle gehen und auf das Parsen warten, aber da er 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 er den OPERATOR, sieht, dass die Regeln besagen "wenn du einen Operator += hast, muss die linke Seite initialisiert worden sein" und sagt "Ups!"

Der Punkt ist hier, dass es hat noch nicht wirklich mit der Analyse der Zeile begonnen . Dies alles geschieht sozusagen in Vorbereitung auf das eigentliche Parsen, so dass der Zeilenzähler noch nicht zur nächsten Zeile vorgerückt ist. Wenn er also den Fehler meldet, denkt er immer noch, dass er in der vorherigen Zeile steht.

Wie ich schon sagte, könnte man argumentieren, dass es sich um einen Fehler in der Benutzerfreundlichkeit handelt, aber eigentlich ist es eine ziemlich häufige Sache. Einige Compiler sind da ehrlicher und sagen "Fehler in oder um Zeile XXX", aber dieser nicht.

53voto

Brian Punkte 112487

Ein Blick auf die Demontage kann Klarheit schaffen:

>>> def f():
...    print a
...    print b
...    a = 1

>>> import dis
>>> dis.dis(f)

  2           0 LOAD_FAST                0 (a)
              3 PRINT_ITEM
              4 PRINT_NEWLINE

  3           5 LOAD_GLOBAL              0 (b)
              8 PRINT_ITEM
              9 PRINT_NEWLINE

  4          10 LOAD_CONST               1 (1)
             13 STORE_FAST               0 (a)
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE

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 erkannt hat, dass a innerhalb der Funktion zugewiesen ist, und es als lokale Variable eingestuft hat. Der Zugriffsmechanismus für lokale Variablen unterscheidet sich grundlegend von dem für globale Variablen - ihnen wird statisch ein Offset in der Variablentabelle des Frames zugewiesen, was bedeutet, dass die Suche über einen schnellen Index erfolgt und nicht wie bei globalen Variablen über ein teureres dict lookup. Aus diesem Grund liest Python die print a Zeile als "ermittle den Wert der lokalen Variable 'a' in Slot 0 und drucke ihn aus", und wenn er feststellt, dass diese Variable noch nicht initialisiert ist, löst er eine Ausnahme aus.

12voto

Mongoose Punkte 4345

Python hat ein recht interessantes Verhalten, wenn man die traditionelle Semantik von globalen Variablen ausprobiert. Ich erinnere mich nicht mehr an die Details, aber man kann den Wert einer Variablen, die im "globalen" Bereich deklariert ist, problemlos lesen, aber wenn man ihn ändern will, muss man die global Stichwort. Versuchen Sie zu ändern test() dazu:

def test():
    global c
    print(a)
    print(b)
    print(c)    # (A)
    c+=1        # (B)

Der Grund für diese Fehlermeldung liegt darin, dass Sie innerhalb dieser Funktion auch eine neue Variable mit demselben Namen wie eine "globale" Variable deklarieren können, die dann völlig unabhängig ist. Der Interpreter denkt, dass Sie versuchen, eine neue Variable in diesem Bereich zu erstellen, die c und alles in einem Arbeitsgang ändern, was in Python nicht erlaubt ist, weil diese neue c nicht initialisiert wurde.

8voto

Sahil kalra Punkte 7248

Das beste Beispiel, das dies verdeutlicht, ist:

bar = 42
def foo():
    print bar
    if False:
        bar = 0

beim Aufruf von foo() dies auch hebt UnboundLocalError obwohl wir nie die Linie erreichen werden bar=0 Daher sollten logischerweise niemals lokale Variablen erstellt werden.

Das Geheimnis liegt in " Python ist eine interpretierte Sprache " und die Deklaration der Funktion foo als eine einzige Anweisung (d.h. eine zusammengesetzte Anweisung) interpretiert wird, wird sie einfach nur dumm interpretiert und lokale und globale Bereiche erstellt. Also bar vor der Ausführung im lokalen Bereich erkannt wird.

Für weitere Beispiele so Lesen Sie 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-Scopings von Variablen:

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X