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 erreichen print(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 mit c=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 den c
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
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. 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.