Poniższy kod działa zgodnie z oczekiwaniami zarówno w Pythonie 2.5, jak i 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()
Kiedy jednak odkomentuję linię (B) , otrzymuję UnboundLocalError: 'c' not assigned
linię na linii (A) . Wartości a
i b
są drukowane poprawnie. To mnie całkowicie zaskoczyło z dwóch powodów:
Dlaczego w linii (A) generowany jest błąd czasu wykonania z powodu późniejszej instrukcji w linii (B) ?
Dlaczego są zmienne
a
ib
drukowane zgodnie z oczekiwaniami, natomiastc
podnosi błąd?
Jedyne wyjaśnienie, jakie mogę wymyślić, to to, że zmienna lokalnac
jest tworzona przez przypisanie c+=1
, które ma pierwszeństwo c
przed zmienną „globalną” nawet przed utworzeniem zmiennej lokalnej. Oczywiście nie ma sensu, aby zmienna „kradła” zasięg, zanim on istnieje.
Czy ktoś mógłby wyjaśnić to zachowanie?
global
lubnonlocal
zmusisz przypisanie globalne lub nielokalne)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)
iprint(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żnec=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 onc
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
dzieli to na coś w rodzaju
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.
źródło
dict
, jest wewnętrznie tylko tablicą (locals()
zapełni siędict
do zwrócenia, ale zmiany w nim nie tworzą nowychlocals
). Faza parsowania polega na znalezieniu każdego przypisania do lokalnego i przekształceniu nazwy do pozycji w tej tablicy oraz wykorzystaniu tej pozycji za każdym razem, gdy następuje odwołanie do nazwy. Po wejściu do funkcji locale niebędące argumentami są inicjowane na symbol zastępczy, iUnboundLocalError
dzieje się tak, gdy zmienna jest czytana, a powiązany z nią indeks nadal ma wartość symbolu zastępczego.Spojrzenie na demontaż może wyjaśnić, co się dzieje:
Jak widać, Kod bajtowy dostępu a jest
LOAD_FAST
, a dla BLOAD_GLOBAL
. Wynika to z tego, że kompilator zidentyfikował, do którego przypisano funkcję a, i sklasyfikował ją jako zmienną lokalną. Mechanizm dostępu dla mieszkańców jest zasadniczo różny dla globałów - statycznie przypisuje im się przesunięcie w tabeli zmiennych ramki, co oznacza, że wyszukiwanie jest szybkim indeksem, a nie droższym wyszukiwaniem dict jak dla globałów. Z tego powodu Python odczytujeprint a
wiersz jako „pobierz wartość zmiennej lokalnej 'a' trzymanej w gnieździe 0 i wydrukuj ją”, a gdy wykryje, że zmienna ta jest nadal niezainicjowana, podnosi wyjątek.źródło
Python zachowuje się dość interesująco, gdy próbujesz tradycyjnej semantyki zmiennych globalnych. Nie pamiętam szczegółów, ale możesz dobrze odczytać wartość zmiennej zadeklarowanej w zakresie „globalnym”, ale jeśli chcesz ją zmodyfikować, musisz użyć
global
słowa kluczowego. Spróbuj zmienićtest()
na:Przyczyną tego błędu jest również to, że możesz również zadeklarować nową zmienną wewnątrz tej funkcji o tej samej nazwie co „globalna”, i byłaby ona całkowicie osobna. Tłumacz interpretuje, że próbujesz utworzyć nową zmienną w tym zakresie o nazwie
c
i zmodyfikować ją w jednej operacji, co nie jest dozwolone w Pythonie, ponieważ ta nowac
nie została zainicjowana.źródło
Najlepszym przykładem, który to wyjaśnia, jest:
gdy dzwonisz
foo()
, to również podnosi,UnboundLocalError
chociaż nigdy nie dojdziemy do liniibar=0
, więc logicznie nie należy nigdy tworzyć zmiennej lokalnej.Tajemnica tkwi w „ Python jest językiem interpretowanym ”, a deklaracja funkcji
foo
jest interpretowana jako pojedyncza instrukcja (tj. Instrukcja złożona), po prostu głupio interpretuje ją i tworzy zakresy lokalne i globalne. Tak więcbar
jest rozpoznawany w zasięgu lokalnym przed wykonaniem.Na więcej przykładów jak to przeczytać ten post: http://blog.amir.rachum.com/blog/2013/07/09/python-common-newbie-mistakes-part-2/
Ten post zawiera pełny opis i analizy zakresu zmiennych Pythona:
źródło
Oto dwa linki, które mogą pomóc
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 pierwszy opisuje błąd UnboundLocalError. Link dwa może pomóc w ponownym zapisaniu funkcji testowej. Na podstawie linku drugiego oryginalny problem można przepisać jako:
źródło
Nie jest to bezpośrednia odpowiedź na twoje pytanie, ale jest ściśle powiązana, ponieważ jest to kolejna problem spowodowany relacją między rozszerzonym przypisaniem a zakresami funkcji.
W większości przypadków myślisz o rozszerzonym przypisaniu (
a += b
) jako dokładnie równoważnym z prostym przypisaniem (a = a + b
). Można jednak mieć z tym kłopoty, w jednym rogu. Pozwól mi wyjaśnić:Sposób, w jaki działa proste przypisanie Pythona, oznacza, że jeśli
a
zostanie przekazany do funkcji (np.func(a)
Zwróć uwagę, że Python jest zawsze przekazywany przez odniesienie), toa = a + b
nie zmodyfikujea
przekazanego. Zamiast tego po prostu zmodyfikuje lokalny wskaźnik doa
.Ale jeśli używasz
a += b
, to czasami jest implementowany jako:lub czasami (jeśli metoda istnieje) jako:
W pierwszym przypadku (o ile
a
nie jest zadeklarowany jako globalny), nie ma żadnych skutków ubocznych poza zasięgiem lokalnym, ponieważ przypisanie doa
jest tylko aktualizacją wskaźnika.W drugim przypadku
a
faktycznie się zmodyfikuje, więc wszystkie odniesienia doa
będą wskazywać na zmodyfikowaną wersję. Pokazuje to następujący kod:Zatem sztuczka polega na uniknięciu rozszerzonego przypisywania argumentów funkcji (staram się używać go tylko do zmiennych lokalnych / pętli). Użyj prostego zadania, a będziesz bezpieczny przed niejednoznacznym zachowaniem.
źródło
Interpreter języka Python odczyta funkcję jako kompletną jednostkę. Myślę o tym jak o czytaniu go w dwóch krokach, raz, aby zebrać jego zamknięcie (zmienne lokalne), a potem ponownie, aby przekształcić go w kod bajtowy.
Jak zapewne już wiesz, każda nazwa używana po lewej stronie „=” jest domyślnie zmienną lokalną. Nieraz byłem przyłapany na zmianie dostępu do zmiennej na + = i nagle jest to inna zmienna.
Chciałem również zauważyć, że tak naprawdę nie ma to nic wspólnego z globalnym zasięgiem. Takie samo zachowanie uzyskuje się w przypadku funkcji zagnieżdżonych.
źródło
c+=1
przypisujec
, python zakłada, że przypisane zmienne są lokalne, ale w tym przypadku nie zostało zadeklarowane lokalnie.Albo użyj
global
albononlocal
słowa kluczowego .nonlocal
działa tylko w Pythonie 3, więc jeśli używasz Pythona 2 i nie chcesz, aby twoja zmienna była globalna, możesz użyć obiektu zmiennego:źródło
Najlepszym sposobem na osiągnięcie zmiennej klasy jest bezpośredni dostęp według nazwy klasy
źródło
W Pythonie mamy podobną deklarację dla wszystkich typów zmiennych lokalnych, zmiennych klasowych i zmiennych globalnych. kiedy odwołujesz zmienną globalną z metody, python myśli, że faktycznie odwołujesz się do zmiennej z samej metody, która nie została jeszcze zdefiniowana, i dlatego generuje błąd. Aby odwołać się do zmiennej globalnej, musimy użyć globals () ['nazwa zmiennej'].
w twoim przypadku użyj globals () ['a], globals () [' b '] i globals () [' c '] zamiast odpowiednio a, b i c.
źródło
Niepokoi mnie ten sam problem. Za pomocą
nonlocal
iglobal
może rozwiązać problem.Jednak uwaga wymagana do użycia
nonlocal
, działa dla zagnieżdżonych funkcji. Jednak na poziomie modułu nie działa. Zobacz przykłady tutaj.źródło