it-swarm.com.de

Fehler im Bereich der Python-Variablen

Der folgende Code funktioniert erwartungsgemäß 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 einen UnboundLocalError: 'c' not assigned in Zeile (A) . Die Werte von a und b werden korrekt gedruckt. Das hat mich aus zwei Gründen völlig verblüfft:

  1. Warum wird in Zeile (A) aufgrund einer späteren Anweisung in Zeile (B) ein Laufzeitfehler ausgegeben?

  2. Warum werden die Variablen a und b erwartungsgemäß gedruckt, während c einen Fehler auslöst?

Die einzige Erklärung, die ich finden kann, ist, dass eine local variable c durch die Zuweisung c+=1 erstellt wird, die Vorrang vor der "globalen" Variablen c hat, noch bevor die lokale Variable erstellt wird. Natürlich ist es für eine Variable nicht sinnvoll, den Gültigkeitsbereich zu "stehlen", bevor sie existiert.

Könnte jemand bitte dieses Verhalten erklären?

185
tba

Python behandelt Variablen in Funktionen unterschiedlich, je nachdem, ob Sie ihnen Werte innerhalb der Funktion zuweisen oder nicht. Wenn eine Funktion Zuweisungen zu einer Variablen enthält, wird sie standardmäßig als lokale Variable behandelt. Wenn Sie die Zeile auskommentieren, versuchen Sie daher, auf eine lokale Variable zu verweisen, bevor ihr ein Wert zugewiesen wurde.

Wenn Sie möchten, dass die Variable c auf die globale c-Anweisung verweist

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 Funktionsumfang zu verweisen, der eine Variable c enthält.

192
recursive

Python ist insofern etwas seltsam, als es alles in einem Wörterbuch für die verschiedenen Bereiche aufbewahrt. Das Original a, b, c befindet sich im obersten Bereich und damit im obersten Wörterbuch. Die Funktion hat ein eigenes Wörterbuch. Wenn Sie die Anweisungen print(a) und print(b) erreichen, enthält das Wörterbuch nichts mit diesem Namen. Daher sucht Python in der Liste und findet sie im globalen Wörterbuch.

Jetzt kommen wir zu c+=1, was natürlich c=c+1 entspricht. Wenn Python diese Zeile durchsucht, heißt es "aha, es gibt eine Variable mit dem Namen c, und ich werde sie in mein lokales Bereichswörterbuch aufnehmen." Wenn es dann nach einem Wert für c für das c auf der rechten Seite der Zuweisung sucht, findet es sein lokale Variable mit dem Namen c, das noch keinen Wert hat, und löst so den Fehler aus.

Die oben erwähnte Anweisung global c teilt dem Parser einfach mit, dass er c aus dem globalen Bereich verwendet und daher keinen neuen benötigt.

Der Grund, warum es heißt, dass 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 noch nicht glaubt, dass es diese Zeile 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 ernst zu nehmen auch.

Wenn es irgendeinen Trost gibt, habe ich wahrscheinlich einen Tag damit verbracht, mit demselben Thema zu graben und zu experimentieren, bevor ich etwas gefunden habe, das Guido über die Wörterbücher geschrieben hat, in denen alles erklärt wurde.

Update, siehe Kommentare:

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

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

c+=1

es zerlegt es in so etwas wie

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

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. Erst dann sieht es der Operator, sieht, dass die Regeln "Wenn Sie einen Operator haben + = die linke Seite muss initialisiert worden sein" und sagt "whoops!"

Der Punkt hier ist, dass es hat das Parsen der Linie noch nicht wirklich begonnen. Dies geschieht alles sozusagen vor der eigentlichen Analyse, sodass der Zeilenzähler nicht zur nächsten Zeile vorgerückt ist. Wenn es also den Fehler meldet, denkt es immer noch an die vorherige Zeile.

Wie ich schon sagte, könnte man behaupten, dass es sich um einen Usability-Fehler handelt, der aber eigentlich ziemlich häufig vorkommt. Einige Compiler sind ehrlicher und sagen "Fehler in oder um Zeile XXX", aber dies ist nicht der Fall.

69
Charlie Martin

Ein Blick auf die Demontage kann klären, 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, 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 Locals unterscheidet sich grundlegend für Globals: Sie haben statisch einen Versatz in der Variablentabelle des Frames. Dies bedeutet, dass Lookup ein schneller Index ist, statt wie bei Globals der teurere Dict-Look. Aus diesem Grund liest Python die print a-Zeile als "holt den Wert der lokalen Variablen 'a' in Steckplatz 0 und druckt sie aus", und wenn sie erkennt, dass diese Variable noch nicht initialisiert ist, wird eine Ausnahme ausgelöst.

42
Brian

Python hat ein ziemlich interessantes Verhalten, wenn Sie die traditionelle Semantik globaler Variablen ausprobieren. Ich kann mich nicht an die Details erinnern, aber Sie können den Wert einer im Bereich 'global' deklarierten Variablen einfach lesen. Wenn Sie ihn jedoch ändern möchten, müssen Sie das Schlüsselwort global verwenden. Versuchen Sie, test() folgendermaßen zu ändern:

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

Sie erhalten diesen Fehler auch deshalb, weil Sie innerhalb dieser Funktion auch eine neue Variable deklarieren können, die denselben Namen wie eine 'globale' hat, und diese wäre völlig getrennt. Der Interpreter glaubt, dass Sie versuchen, eine neue Variable mit dem Namen c zu erstellen und diese in einem einzigen Vorgang zu ändern. Dies ist in Python nicht zulässig, da diese neue c nicht initialisiert wurde.

10
Mongoose

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-wenn-der-variable-has-a-value

2: docs.python.org/3.1/faq/programming.html?highlight=nonlocal#how-do-i-write-a-function-mit-output-parameters-call-by-referenz

link eins beschreibt den Fehler UnboundLocalError. Link zwei kann beim erneuten Schreiben 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)
5
mcdon

Das beste Beispiel, das klar macht, ist:

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

wenn Sie foo() aufrufen, wirft dies auch raisesUnboundLocalError auf, obwohl wir niemals die Zeile bar=0 erreichen werden. Daher sollten logisch lokale Variablen niemals erstellt werden.

Das Rätsel liegt in "Python ist eine interpretierte Sprache" und die Deklaration der Funktion foo wird als eine einzige Anweisung (d. H. Als eine zusammengesetzte Anweisung) interpretiert. Sie interpretiert sie einfach dumm und erstellt lokale und globale Bereiche. Daher wird bar vor der Ausführung im lokalen Bereich erkannt.

Für weitere Beispiele wie folgt 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-Scopes von Variablen:

5
Sahil kalra

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 Funktionsbereichen verursacht wird.

In den meisten Fällen denken Sie an eine erweiterte Zuordnung (a += b), die der einfachen Zuordnung (a = a + b) genau entspricht. Es ist jedoch möglich, dass Sie in einer Ecke Probleme bekommen. Lassen Sie mich erklären:

Die einfache Zuweisung von Python bedeutet, dass, wenn a an eine Funktion übergeben wird (wie func(a); beachten Sie, dass Python immer pass-by-reference ist), a = a + b die übergebene a nicht ändert lokaler Zeiger auf a

Wenn Sie jedoch a += b verwenden, wird es manchmal wie folgt implementiert:

a = a + b

oder manchmal (falls die Methode existiert) als:

a.__iadd__(b)

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

Im zweiten Fall ändert sich a tatsächlich selbst, sodass alle Verweise auf a auf die geänderte Version zeigen. Dies wird durch den folgenden Code demonstriert:

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, eine erweiterte Zuordnung von Funktionsargumenten zu vermeiden (ich versuche, sie nur für lokale/Schleifenvariablen zu verwenden). Verwenden Sie einfache Zuweisungen, und Sie sind vor mehrdeutigem Verhalten geschützt. 

3
alsuren

Der Python-Interpreter liest eine Funktion als vollständige Einheit. Ich denke, es ist wie das Lesen in zwei Durchläufen, einmal um die Schließung (die lokalen Variablen) zu erfassen und dann wieder in Bytecode umzuwandeln.

Wie Sie sicher wissen, war Ihnen bereits bekannt, dass jeder Name links von '=' implizit eine lokale Variable ist. Mehr als einmal wurde ich herausgefunden, als ich den Zugriff einer Variablen auf a = änderte, 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. Sie erhalten dasselbe Verhalten mit verschachtelten Funktionen.

2
James Hopkin

c+=1 weist c zu. Python geht davon aus, dass die zugewiesenen Variablen lokal sind. In diesem Fall wurde sie jedoch nicht lokal deklariert.

Verwenden Sie entweder die Schlüsselwörter global oder nonlocal

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änderliches Objekt verwenden:

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

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

test()
2
Colegram

Klassenvariable erreichen Sie am besten direkt über den Klassennamen

class Employee:
    counter=0

    def __init__(self):
        Employee.counter+=1
1
Harun ERGUL

In Python haben wir eine ähnliche Deklaration für alle lokalen, Klassenvariablen und globalen Variablen. Wenn Sie eine globale Variable aus einer Methode referenzieren, ist Python der Meinung, dass Sie tatsächlich eine Variable aus der Methode selbst referenzieren, die noch nicht definiert ist. Daher werfen Sie den Fehler . globals () ['Variablenname'].

verwenden Sie in Ihrem Fall globals () ['a], globals () [' b '] und globals () [' c '] anstelle von a, b und c.

0
Santosh Kadam