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

6voto

Karl Knechtel Punkte 55450

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).

4voto

mcdon Punkte 4803

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 one beschreibt den Fehler UnboundLocalError. Link zwei kann beim Umschreiben Ihrer Testfunktion helfen. Anhand von Link zwei könnte das ursprüngliche Problem wie folgt umgeschrieben werden:

>>> a, b, c = (1, 2, 3)
>>> print (a, b, c)
(1, 2, 3)
>>> def test (a, b, c):
...     print (a)
...     print (b)
...     print (c)
...     c += 1
...     return a, b, c
...
>>> a, b, c = test (a, b, c)
1
2
3
>>> print (a, b ,c)
(1, 2, 4)

2voto

James Hopkin Punkte 13389

Der Python-Interpreter liest eine Funktion als komplette Einheit. Ich stelle mir das so vor, dass er sie in zwei Durchgängen liest, einmal, um ihre Schließung (die lokalen Variablen) zu erfassen, und dann noch einmal, um sie in Byte-Code zu verwandeln.

Wie Sie sicher schon wussten, ist jeder Name, der links von einem '=' verwendet wird, implizit eine lokale Variable. Mehr als einmal wurde ich dabei erwischt, wie ich den Zugriff auf eine Variable durch ein += änderte und sie plötzlich eine andere Variable war.

Ich wollte auch darauf hinweisen, es ist nicht wirklich etwas zu tun mit globalen Bereich speziell. Sie erhalten das gleiche Verhalten mit verschachtelten Funktionen.

2voto

alsuren Punkte 170

Dies ist keine direkte Antwort auf Ihre Frage, aber es ist eng damit verbunden, da es sich um ein weiteres Problem handelt, das durch die Beziehung zwischen erweiterter Zuweisung und Funktionsbereichen verursacht wird.

In den meisten Fällen denkt man eher an eine erweiterte Zuweisung ( a += b ) als genau gleichwertig zur einfachen Zuweisung ( a = a + b ). Es ist jedoch möglich, damit in einer bestimmten Ecke Probleme zu bekommen. Lassen Sie mich das erklären:

Die Art und Weise, wie Pythons einfache Zuweisung funktioniert, bedeutet, dass wenn a an eine Funktion übergeben wird (wie func(a) (beachten Sie, dass Python immer eine Pass-by-Reference-Funktion hat), dann a = a + b ändert nicht die a die übergeben wird. Stattdessen wird nur der lokale Zeiger auf a .

Aber wenn Sie die a += b dann wird es manchmal so umgesetzt:

a = a + b

oder manchmal (wenn die Methode existiert) als:

a.__iadd__(b)

Im ersten Fall (solange die a nicht als global deklariert ist), gibt es keine Nebeneffekte außerhalb des lokalen Bereichs, da die Zuweisung an a ist nur eine Zeigeraktualisierung.

Im zweiten Fall, a wird sich tatsächlich selbst ändern, so dass alle Verweise auf a verweist auf die geänderte Version. Dies wird durch den folgenden Code veranschaulicht:

def copy_on_write(a):
      a = a + a
def inplace_add(a):
      a += a
a = [1]
copy_on_write(a)
print a # [1]
inplace_add(a)
print a # [1, 1]
b = 1
copy_on_write(b)
print b # [1]
inplace_add(b)
print b # 1

Der Trick besteht also darin, die erweiterte Zuweisung von Funktionsargumenten zu vermeiden (ich versuche, sie nur für lokale Variablen/Schleifenvariablen zu verwenden). Verwenden Sie eine einfache Zuweisung, und Sie sind vor zweideutigem Verhalten sicher.

2voto

Colegram Punkte 106

c+=1 vergibt c Python nimmt an, dass zugewiesene Variablen lokal sind, aber in diesem Fall wurde sie nicht lokal deklariert.

Entweder verwenden Sie die global o nonlocal Schlüsselwörter.

nonlocal funktioniert nur in Python 3, wenn Sie also Python 2 verwenden und Ihre Variable nicht global machen wollen, können Sie ein veränderbares Objekt verwenden:

my_variables = { # a mutable object
    'c': 3
}

def test():
    my_variables['c'] +=1

test()

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