Pytanie Dlaczego nieprawidłowe przypisanie do zmiennej globalnej powoduje podniesienie wyjątku wcześniej?


a = 10
def f():
  print(1)
  print(a) # UnboundLocalError raised here
  a = 20
f()

Ten kod oczywiście podnosi UnboundLocalError: local variable 'a' referenced before assignment. Ale dlaczego ten wyjątek został podniesiony na print(a) linia?

Jeśli interpreter wykonał kod linia po linii (tak jak myślałem), to nie wiedziałby, że coś było nie tak print(a) został osiągnięty; po prostu by tak pomyślał a odnoszą się do zmiennej globalnej.

Wygląda na to, że tłumacz odczytuje z góry całą funkcję, aby dowiedzieć się, czy a służy do przypisania. Czy to jest udokumentowane w dowolnym miejscu? Czy jest jakaś inna sytuacja, w której tłumacz patrzy w przyszłość (oprócz sprawdzania błędów składni)?

Aby wyjaśnić, sam wyjątek jest całkowicie jasny: zmienne globalne można odczytać bez global deklaracja, ale nie napisana (ta konstrukcja zapobiega błędom spowodowanym niechcianą modyfikacją zmiennych globalnych, błędy te są szczególnie trudne do debugowania, ponieważ prowadzą do błędów, które występują z dala od lokalizacji błędnego kodu). Jestem tylko ciekawy, dlaczego wyjątek został zgłoszony wcześniej.


14
2017-11-26 19:09


pochodzenie


Myślę, że przekonasz się, że python nie może domyślnie uzyskać dostępu do zmiennych globalnych z funkcji wewnętrznych. Będziesz musiał wyraźnie zadeklarować, że chcesz używać globalnego. (btw Nie używaj globalnych zmiennych). - quamrana
@quamrana nieprawdziwe. Jeśli usuniesz zadanie do lokalnego a kod zostanie wydrukowany 10. - 2rs2ts
Co to jest warte, nie jest to specyficzne dla Pythona 3, po prostu testowane w Pythonie 2 i to samo się dzieje. - 2rs2ts
Myślę, że przekonasz się, że python nie może (edytować) <strike> dostęp </ strike> Napisz do globalne zmienne z funkcji wewnętrznych domyślnie. Będziesz musiał wyraźnie zadeklarować, że chcesz używać globalnego. (btw Nie używaj globalnych zmiennych). - quamrana
Istnieje wiele podobnych / duplikatów, takich jak to ale ponieważ ten Q nie ma kodu redundantnego, myślę, że nie powinien być zamknięty. - Fermi paradox


Odpowiedzi:


Według Dokumentacja Pythona, tłumacz najpierw zauważy przypisanie dla zmiennej o nazwie a w zasięgu f() (bez względu na pozycję przypisania w funkcji), a następnie w konsekwencji rozpoznaje tylko zmienną a jako zmienna lokalna w tym zakresie. To zachowanie skutecznie cienie zmienna globalna a.

Wyjątek jest wtedy podnoszony "wczesny", ponieważ interpreter, który wykonuje kod "linia po linii", napotka instrukcję print odwołującą się do zmiennej lokalnej, która nie jest jeszcze powiązana w tym momencie (pamiętaj, że Python szuka local zmienne tutaj).

Jak wspomniałeś w swoim pytaniu, musisz użyć global słowo kluczowe, aby wyraźnie powiedzieć kompilatorowi, że przypisanie w tym zakresie odbywa się do zmiennej globalnej, prawidłowy kod będzie:

a = 10
def f():
  global a
  print(1)
  print(a) # Prints 10 as expected
  a = 20
f()

Tak jak @ 2rs2ts powiedział w usuniętej odpowiedzi, łatwo to wytłumaczyć faktem, że "Python nie jest po prostu interpretowany, jest on kompilowany do kodu bajtowego, a nie tylko interpretowany wiersz po wierszu".


14
2017-11-26 19:28



Czy fakt, że skrypt jest najpierw kompilowany do kodu bajtowego, a nie interpretowany linia po linii ma jakieś inne (nieco) nieoczekiwane konsekwencje? - max
Nie, żebym wiedział o tym z mojej głowy, ale prawdopodobnie jestem w błędzie. Coś, co wiem, jest takie, że python nie jest zoptymalizowany przez kompilator, ponieważ język jest bardzo dynamiczny (brak typów statycznych, metody mogą być zastępowane w locie, itp.), Co sprawia, że ​​jest to bardzo trudne. Ogólnie rzecz biorąc, kod jest kompilowany do kodu bajtowego tylko po to, aby usunąć czas analizowania przy każdym wywołaniu funkcji (środowisko wykonawcze szuka tylko bajtów w dużej tabeli odnośników, zamiast analizować wszystko ponownie). cc @max - Antoine Bolvy
@max: Kompilacja i interpretacja są całkowicie i całkowicie nieistotne dla twojego pytania. Pytasz o to Semantyka, tj. specyfikacja języka Python. Specyfikacja języka Python mówi, że wszystkie zmienne przypisane do funkcji są lokalne, chyba że zostały zadeklarowane global. Kropka. Kompilacja i interpretacja są kwestią Pragmatyka, tj. dowolna konkretna implementacja Pythona. Jednak każda konkretna implementacja Pythona, niezależnie od tego, czy jest kompilowana czy interpretowana, musi być zgodna ze specyfikacją języka Python. W przeciwnym razie nie być "Implementacja Pythona", to ... - Jörg W Mittag
... byłaby implementacją zupełnie innego języka, który w pewien sposób przypomina Python. Nawet jeśli nie było żadnych implementacji Pythona w ogóle, jeśli Python istniał tylko na papierze (lub nawet tylko w głowie Guida van Rossuma), zachowanie nadal byłoby tym samym, co widzisz, ponieważ specyfikacja języka Python mówi, że zachowanie, które ty są widzenie jest tym, którego ty powinien zobaczyć. Nie należy mylić języka programowania "Python" z implementacją "CPython". Np. Obserwowane zachowanie jest częścią "Python" i tym samym identyczne wszystko wdrożenia (Pyston, ... - Jörg W Mittag
... PyPy, IronPython, Jython, CPython, Pynie, ...), podczas gdy liczenie odwołań i deterministyczna finalizacja, GIL, API rozszerzenia C są stroną "CPython" i niekoniecznie istnieją w innych implementacjach. - Jörg W Mittag


w Rozdzielczość nazw sekcja podręcznika Python Reference Manual:

[..] Jeśli bieżący zakres jest zasięgiem funkcji i nazwa odnosi się do zmiennej lokalnej, która nie została jeszcze powiązana z wartością w miejscu, w którym nazwa jest używana, UnboundLocalError wyjątek jest podniesiony [..]

to jest oficjalne słowo na kiedy UnboundLocalError występuje. Jeśli spojrzysz na kod bajtowy, generowany będzie CPython dla twojej funkcji f z dis możesz zobaczyć, jak próbuje załadować nazwę z zakresu lokalnego, gdy jej wartość nie została jeszcze ustawiona:

>>> dis.dis(f)
  3           0 LOAD_GLOBAL              0 (print)
              3 LOAD_CONST               1 (1)
              6 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
              9 POP_TOP

  4          10 LOAD_GLOBAL              0 (print)
             13 LOAD_FAST                0 (a)      # <-- this command
             16 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             19 POP_TOP

  5          20 LOAD_CONST               2 (20)
             23 STORE_FAST               0 (a)
             26 LOAD_CONST               0 (None)
             29 RETURN_VALUE

Jak widać, imię 'a' jest ładowany na stos za pomocą LOAD_FAST dowództwo:

             13 LOAD_FAST                0 (a)

To jest polecenie, które służy do przechwytywania lokalny zmienne w funkcji (o nazwie FAST ze względu na to, że jest to znacznie szybsze niż ładowanie z globalnego zasięgu za pomocą LOAD_GLOBAL).

To naprawdę nie ma nic wspólnego z globalną nazwą a który został wcześniej zdefiniowany. Ma to związek z tym, że CPython zakłada, że ​​grasz dobrze i generujesz LOAD_FAST dla odniesień do 'a' od 'a' jest bycie przypisane do (tj. nazwa lokalna) wewnątrz ciała funkcji.

Dla funkcji z dostępem do jednej nazwy i bez odpowiedniego przypisania, CPython nie generuje LOAD_FAST zamiast tego idzie i patrzy na zasięg globalny z LOAD_GLOBAL:

>>> def g():
...    print(b)
>>> dis.dis(g)
  2           0 LOAD_GLOBAL              0 (print)
              3 LOAD_GLOBAL              1 (b)
              6 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
              9 POP_TOP
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE

Wygląda na to, że tłumacz odczytuje z góry całą funkcję, aby dowiedzieć się, czy a służy do przypisania. Czy to jest udokumentowane w dowolnym miejscu? Czy jest jakaś inna sytuacja, w której tłumacz patrzy w przyszłość (oprócz sprawdzania błędów składni)?

W sekcji Instrukcje złożone w podręczniku referencyjnym dla definicji funkcji podano:

Definicja funkcji jest instrukcją wykonywalną. Jego wykonanie wiąże nazwę funkcji w bieżącej lokalnej przestrzeni nazw z obiektem funkcji (opakowanie wokół wykonywanego kodu dla funkcji).

W szczególności wiąże nazwę f do obiektu funkcji przechowującego skompilowany kod, f.__code__, to dis upiększa dla nas.


9
2017-11-26 20:07