Python jest trochę dziwny, ponieważ przechowuje wszystko w słowniku dla różnych zakresów. Oryginały a, b, c znajdują się w najwyższym zakresie, a więc w tym najwyższym słowniku. Funkcja ma własny słownik. Kiedy dotrzesz do instrukcji print(a)
i print(b)
, w słowniku nie ma nic o tej nazwie, więc Python sprawdza listę i znajduje je w słowniku globalnym.
Teraz dochodzimy do c+=1
, co oczywiście jest równoważne c=c+1
. Kiedy Python skanuje tę linię, mówi „aha, jest zmienna o nazwie c, wstawię ją do mojego lokalnego słownika zakresu”. Następnie, gdy szuka wartości c dla c po prawej stronie przypisania, znajduje swoją lokalną zmienną o nazwie c , która nie ma jeszcze żadnej wartości, i zgłasza błąd.
Powyższe stwierdzenie global c
po prostu mówi parserowi, że używa on c
z zakresu globalnego, a zatem nie potrzebuje nowego.
Powodem, dla którego mówi, że jest problem z linią, którą robi, jest to, że efektywnie szuka nazw, zanim spróbuje wygenerować kod, więc w pewnym sensie nie sądzi, że tak naprawdę robi tę linię. Twierdziłbym, że jest to błąd użyteczności, ale ogólnie dobrą praktyką jest po prostu nauczyć się nie brać wiadomości kompilatora zbyt poważnie.
Jeśli to wygoda, spędziłem prawdopodobnie dzień na kopaniu i eksperymentowaniu z tym samym zagadnieniem, zanim znalazłem coś, co Guido napisał o słownikach, które wyjaśniają wszystko.
Aktualizacja, patrz komentarze:
Nie skanuje kodu dwa razy, ale skanuje kod w dwóch fazach: leksykalnym i parsującym.
Zastanów się, jak działa parsowanie tego wiersza kodu. Lexer odczytuje tekst źródłowy i dzieli go na leksemy, „najmniejsze elementy” gramatyki. Więc kiedy dojdzie do linii
c+=1
dzieli to na coś w rodzaju
SYMBOL(c) OPERATOR(+=) DIGIT(1)
Analizator składni ostatecznie chce przekształcić to w drzewo analizujące i wykonać je, ale ponieważ jest to zadanie, zanim to zrobi, szuka nazwy c w lokalnym słowniku, nie widzi go i wstawia do słownika, oznaczając jest niezainicjowany. We w pełni skompilowanym języku po prostu wszedłby do tabeli symboli i czekał na analizę, ale ponieważ nie będzie miał luksusu drugiego przejścia, leksykon wykonuje trochę dodatkowej pracy, aby ułatwić życie później. Tylko wtedy widzi OPERATORA, widzi, że zasady mówią „jeśli masz operatora + = lewa strona musiała zostać zainicjowana” i mówi „ups!”
Chodzi o to, że tak naprawdę jeszcze nie zaczął analizować linii . To wszystko dzieje się jako rodzaj przygotowania do rzeczywistej analizy, więc licznik linii nie przejął się do następnej linii. Zatem, gdy sygnalizuje błąd, nadal myśli, że jest w poprzedniej linii.
Jak mówię, można argumentować, że jest to błąd użyteczności, ale w rzeczywistości jest to dość powszechna rzecz. Niektóre kompilatory są bardziej uczciwe i mówią „błąd w linii XXX lub w jej pobliżu”, ale ten nie.