UnboundLocalError beim Versuch, eine (angeblich globale) Variable zu verwenden, die (neu) zugewiesen wird (auch nach der ersten Verwendung)

Lesezeit: 10 Minuten

Benutzeravatar von tba
tba

Wenn ich diesen Code versuche:

a, b, c = (1, 2, 3)

def test():
    print(a)
    print(b)
    print(c)
    c += 1
test()

Ich erhalte eine Fehlermeldung von der print(c) Zeile, die sagt:

UnboundLocalError: local variable 'c' referenced before assignment

in neueren Versionen von Python, oder

UnboundLocalError: 'c' not assigned

in einigen älteren Versionen.

Wenn ich auskommentiere c += 1beide prints sind erfolgreich.

Ich verstehe nicht: warum druckt a Und b Arbeit, wenn c nicht? Wie hast c += 1 Ursache print(c) zu scheitern, auch wenn es später im Code kommt?

Es scheint die Aufgabe zu sein c += 1 schafft ein lokal Variable cdie Vorrang vor der globalen hat c. Aber wie kann eine Variable den Geltungsbereich “stehlen”, bevor sie existiert? Warum ist c anscheinend lokal hier?


Siehe auch Globale Variablen in einer Funktion verwenden für Fragen, die sich einfach darauf beziehen, wie man eine globale Variable innerhalb einer Funktion neu zuweist, und Ist es möglich, eine Variable in Python zu ändern, die sich in einem äußeren (einschließenden), aber nicht globalen Bereich befindet? zur Neuzuweisung aus einer umschließenden Funktion (Closure).

Siehe Warum wird das Schlüsselwort „global“ nicht benötigt, um auf eine globale Variable zuzugreifen? für Fälle, in denen OP erwartet ein fehler aber nicht Holen Sie sich eine, indem Sie einfach auf eine globale zugreifen, ohne die global Stichwort.

Siehe Wie kann ein Name in Python “ungebunden” sein? Welcher Code kann einen `UnboundLocalError` verursachen? für Fälle, in denen OP erwartet die Variable als lokal, hat aber einen logischen Fehler, der eine Zuweisung in jedem Fall verhindert.

Benutzeravatar von recursive
rekursiv

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 die Zeile auskommentieren, versuchen Sie daher, auf die lokale Variable zu verweisen c bevor ihr irgendein Wert zugewiesen wurde.

Wenn Sie die Variable wollen c sich auf das Globale beziehen c = 3 vor der Funktion zugewiesen, put

global c

als erste Zeile der Funktion.

Wie für Python 3 gibt es jetzt

nonlocal c

die Sie verwenden können, um auf den nächstgelegenen umschließenden Funktionsumfang zu verweisen, der über a verfügt c Variable.

  • Danke. Schnelle Frage. Bedeutet dies, dass Python den Geltungsbereich jeder Variablen vor dem Ausführen eines Programms entscheidet? Vor dem Ausführen einer Funktion?

    – tba

    16. Dezember 2008 um 3:46 Uhr

  • Die Entscheidung über den Gültigkeitsbereich der Variablen wird vom Compiler getroffen, der normalerweise einmal ausgeführt wird, wenn Sie das Programm zum ersten Mal starten. Beachten Sie jedoch, dass der Compiler möglicherweise auch später ausgeführt wird, wenn Sie “eval”- oder “exec”-Anweisungen in Ihrem Programm haben.

    – Greg Hewgill

    16. Dezember 2008 um 3:48 Uhr

  • Okay, danke. Ich denke, “interpretierte Sprache” impliziert nicht ganz so viel, wie ich gedacht hatte.

    – tba

    16. Dezember 2008 um 3:53 Uhr

  • Ah, das Schlüsselwort ‘nonlocal’ war genau das, wonach ich gesucht hatte, es schien, als würde Python dies vermissen. Vermutlich wird dies durch jeden umschließenden Bereich ‘kaskadiert’, der die Variable mit diesem Schlüsselwort importiert?

    – Brenda

    17. November 2009 um 20:54 Uhr

  • @brainfsck: Es ist am einfachsten zu verstehen, wenn Sie zwischen “Nachschlagen” und “Zuweisen” einer Variablen unterscheiden. Die Suche greift auf einen höheren Bereich zurück, wenn der Name im aktuellen Bereich nicht gefunden wird. Die Zuweisung erfolgt immer im lokalen Bereich (es sei denn, Sie verwenden global oder nonlocal um eine globale oder nichtlokale Zuweisung zu erzwingen)

    – Stefan

    13. September 2011 um 12:00 Uhr

Benutzeravatar von Charlie Martin
Charly Martin

Python ist insofern etwas seltsam, als es alles in einem Wörterbuch für die verschiedenen Bereiche aufbewahrt. Die ursprünglichen a, b, c befinden sich im obersten Gültigkeitsbereich und somit im obersten Wörterbuch. Die Funktion hat ein eigenes Wörterbuch. Wenn Sie die erreichen print(a) Und print(b) -Anweisungen gibt es nichts mit diesem Namen im Wörterbuch, also schlägt Python die Liste nach und findet sie im globalen Wörterbuch.

Jetzt kommen wir dazu c+=1was natürlich äquivalent ist c=c+1. Wenn Python diese Zeile scannt, sagt es “aha, es gibt eine Variable namens c, ich füge sie in mein lokales Wörterbuch ein.” 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 cdie noch keinen Wert hat und daher den Fehler auslöst.

Die Aussage global c oben erwähnt teilt dem Parser einfach mit, dass er die verwendet c aus dem globalen Geltungsbereich und benötigt daher keinen neuen.

Der Grund, warum es sagt, dass es ein Problem in der Zeile gibt, die es tut, ist, dass es effektiv nach den Namen sucht, bevor es versucht, Code zu generieren, und so in gewissem Sinne glaubt, dass es diese Zeile noch nicht wirklich macht. Ich würde argumentieren, dass dies ein Usability-Bug ist, aber es ist im Allgemeinen eine gute Praxis, einfach zu lernen, die Nachrichten eines Compilers nicht zu akzeptieren zu ernsthaft.

Falls es Sie tröstet, ich verbrachte wahrscheinlich einen ganzen Tag damit, mit demselben Problem 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:

Es scannt den Code nicht zweimal, aber es scannt den Code in zwei Phasen, Lexing und Parsing.

Überlegen Sie, wie die Analyse dieser Codezeile funktioniert. Der Lexer liest den Ausgangstext und zerlegt ihn in Lexeme, die „kleinsten Bestandteile“ der Grammatik. Also, wenn es auf die Linie trifft

c+=1

es zerlegt es in etwas wie

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

Der Parser möchte daraus schließlich einen Parse-Baum machen und ausführen, aber da es sich um eine Zuweisung handelt, sucht er vorher im lokalen Wörterbuch nach dem Namen c, sieht ihn nicht und fügt ihn markierend in das Wörterbuch ein 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, muss die linke Seite initialisiert worden sein” und sagt “hoppla!”

Der Punkt hier ist, dass es hat noch nicht wirklich mit dem Parsen der Zeile begonnen. Dies geschieht alles als Vorbereitung auf die eigentliche Analyse, sodass der Zeilenzähler noch nicht zur nächsten Zeile vorgerückt ist. Wenn es also den Fehler signalisiert, denkt es immer noch, dass es sich um die vorherige Zeile handelt.

Wie gesagt, man könnte argumentieren, dass es sich um einen Usability-Bug 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 tut es nicht.

  • Hinweis zu Implementierungsdetails: In CPython wird der lokale Geltungsbereich normalerweise nicht als dictes ist intern nur ein Array (locals() wird a bevölkern dict zurückzugeben, aber Änderungen daran schaffen keine neuen locals). Die Analysephase besteht darin, jede Zuweisung zu einem lokalen Objekt zu finden und den Namen in die Position in diesem Array umzuwandeln und diese Position zu verwenden, wenn auf den Namen verwiesen wird. Beim Eintritt in die Funktion werden Nicht-Argument-Locals mit einem Platzhalter und initialisiert UnboundLocalErrors passieren, wenn eine Variable gelesen wird und ihr zugehöriger Index noch den Platzhalterwert hat.

    – ShadowRanger

    25. März 2016 um 19:49 Uhr


  • Python 3.x speichert keine lokalen Variablen in einem Wörterbuch. Das Ergebnis von locals() wird im laufenden Betrieb berechnet. Deshalb wird der Fehler aufgerufen UnboundLocalError Erstens: Die lokale Variable existiert in dem Sinne, wie sie war reserviert, wenn die Funktion kompiliert wurde, wurde aber noch nicht gebunden (zugewiesen). Dies funktioniert grundlegend anders, als dem globalen Namensraum etwas hinzuzufügen (was Ist praktisch ein Wörterbuch), daher würde es keinen Sinn machen, das Problem als generisch zu melden NameError.

    – Karl Knechtel

    7. Februar um 1:08

  • Das ist genau das, was ich gesagt habe, modulo das Implementierungsdetail, wie die locals() präsentiert werden.

    – Charly Martin

    13. Februar um 22:18 Uhr

  • Oh, und diese Antwort ist buchstäblich 15 Jahre alt.

    – Charly Martin

    13. Februar um 22:19 Uhr

Ein Blick auf die Demontage kann verdeutlichen, was passiert:

>>> 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, lautet der Bytecode für den Zugriff auf a LOAD_FASTund 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 klassifiziert hat. Der Zugriffsmechanismus für Locals unterscheidet sich grundlegend von 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 Dict-Suche wie bei Globals. Aus diesem Grund liest Python die print a Zeile als “erhalte den Wert der lokalen Variablen ‘a’, die in Steckplatz 0 gehalten wird, und drucke ihn”, und wenn sie feststellt, dass diese Variable noch nicht initialisiert ist, wird eine Ausnahme ausgelöst.

Benutzeravatar von Mongoose
Mungo

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 Bereich „global“ deklariert ist, problemlos lesen, aber wenn Sie ihn ändern möchten, müssen Sie die verwenden global Stichwort. Versuchen Sie, sich zu ändern test() dazu:

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

Der Grund, warum Sie diesen Fehler erhalten, ist auch, dass Sie innerhalb dieser Funktion auch eine neue Variable mit demselben Namen wie eine “globale” deklarieren können, und diese wäre völlig separat. Der Interpreter denkt, dass Sie versuchen, eine neue Variable in diesem Bereich namens zu erstellen c und ändern Sie alles in einem Vorgang, was in Python nicht zulässig ist, da dies neu ist c wurde nicht initialisiert.

Das beste Beispiel, das es deutlich macht, ist:

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

beim Anrufen foo() das auch erhöht UnboundLocalError obwohl wir niemals die Linie erreichen werden bar=0also sollte logischerweise niemals eine lokale Variable erstellt werden.

Das Geheimnis liegt in “Python ist eine interpretierte Sprache” und die Deklaration der Funktion foo als eine einzelne Anweisung (dh eine zusammengesetzte Anweisung) interpretiert wird, interpretiert sie sie nur dumm und erstellt lokale und globale Gültigkeitsbereiche. So bar wird vor der Ausführung im lokalen Geltungsbereich erkannt.

Für mehr 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 Analysen des Python-Bereichs von Variablen:

  • Python ist nicht mehr “interpretiert” als Java oder C#, und in der Tat die Entscheidung zu behandeln bar als lokale Variable in diesem Code erfordert ein Up-Front-Kompilierungsschritt.

    – Karl Knechtel

    9. September 2022 um 9:37 Uhr

Benutzeravatar von Daniel X Moore
Daniel X Moore

Hier sind zwei Links, die helfen können

1: docs.python.org/3.1/faq/programming.html?highlight=nonlocal#warum-erhalte-ich-einen-ungebundenenlokalenFehler-wenn-die-Variable-einen-Wert-hat

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. Basierend auf Link zwei könnte das ursprüngliche Problem umgeschrieben werden als:

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

  • Python ist nicht mehr “interpretiert” als Java oder C#, und in der Tat die Entscheidung zu behandeln bar als lokale Variable in diesem Code erfordert ein Up-Front-Kompilierungsschritt.

    – Karl Knechtel

    9. September 2022 um 9:37 Uhr

Benutzeravatar von James Hopkin
James Hopkins

Der Python-Interpreter liest eine Funktion als vollständige Einheit. Ich denke, es wird in zwei Durchgängen gelesen, einmal, um seinen Abschluss (die lokalen Variablen) zu sammeln, und dann noch einmal, 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 dabei erwischt, wie ich einen Variablenzugriff auf ein += geändert habe, und es ist plötzlich eine andere Variable.

Ich wollte auch darauf hinweisen, dass es nicht wirklich etwas mit dem globalen Geltungsbereich zu tun hat. Das gleiche Verhalten erhalten Sie mit verschachtelten Funktionen.

1443370cookie-checkUnboundLocalError beim Versuch, eine (angeblich globale) Variable zu verwenden, die (neu) zugewiesen wird (auch nach der ersten Verwendung)

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy