2016-11-26 43 views
14
a = 10 
def f(): 
    print(1) 
    print(a) # UnboundLocalError raised here 
    a = 20 
f() 

Kod ten oczywiście podnosi UnboundLocalError: local variable 'a' referenced before assignment. Ale dlaczego ten wyjątek został podniesiony na linii print(a)?Dlaczego nieprawidłowe przypisanie do zmiennej globalnej powoduje podniesienie wyjątku wcześniej?

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

Wygląda na to, że tłumacz odczytuje z góry całą funkcję, aby ustalić, czy do przypisania należy użyć a. 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)?

Wyjaśnienie, wyjątek sam w sobie jest całkowicie jasny: zmienne globalne można odczytać bez deklaracji global, ale nie zostały napisane (ten projekt zapobiega błędom spowodowanym niezamierzoną modyfikacją zmiennych globalnych, te błędy 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.

+1

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

+6

@quamrana nieprawdziwe. Jeśli usuniesz przypisanie do lokalnego 'a', kod wypisze' 10'. – 2rs2ts

+0

Co to jest warte, nie jest to specyficzne dla Pythona 3, tylko testowane w Pythonie 2 i to samo się dzieje. – 2rs2ts

Odpowiedz

14

Według Python's documentation, interpreter będzie najpierw zauważyć przypisania zmiennej o nazwie a w zakresie f() (bez względu na położenie powierzenie funkcji), a następnie jak tylko konsekwencją uznania zmiennej a jako lokalny zmienna w tym zakresie. To zachowanie skutecznie jest zmienną globalną.

Wyjątek jest następnie wywoływany "wcześniej", 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 dla zmiennej local).

Jak wspomniano w pytaniu, trzeba użyć słowa kluczowego global wyraźnie poinformować kompilator, że przypisanie w tym zakresie odbywa się do zmiennej globalnej prawidłowy kod byłoby:

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

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".

+0

Czy fakt, że skrypt jest najpierw kompilowany do kodu bajtowego, a nie interpretowany linia po linii ma jakieś inne (nieco) nieoczekiwane konsekwencje? – max

+0

Nie to, że jestem świadomy 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 –

+1

@max: Kompilacja i interpretacja są całkowicie i całkowicie nieistotne dla twojego pytania. Pytasz o * Semantykę *, tj. Specyfikację języka Python. Specyfikacja języka Python mówi, że wszystkie zmienne przypisane w funkcji są lokalne, chyba że zostały zadeklarowane jako "globalne". Kropka. Kompilacja i interpretacja to kwestia * Pragmatics *, czyli konkretnej implementacji 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łaby * "implementacją Pythona", to ... –

8

W sekcji Resolution of Names Podręcznika Python Reference to stwierdził:

[..] Jeśli aktualny zakres jest zakres funkcji, a nazwa odnosi się do zmiennej lokalnej, która nie została jeszcze związany z wartością w punkcie, w którym ta nazwa jest używana wyjątek UnboundLocalError jest podniesiona [..]

to oficjalna słowo na kiedy występuje UnboundLocalError.Jeśli spojrzeć na kod bajtowy CPython generuje dla funkcji f z dis widać, że próbuje załadować nazwy z zakresu lokalnego, gdy jej wartość nie została jeszcze nawet ustawić:

>>> 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 można widać, nazwa 'a' ładowany jest na stos za pomocą polecenia LOAD_FAST:

   13 LOAD_FAST    0 (a) 

jest to komenda, która służy do chwycić lokalnych zmienne w funkcji (o nazwie FAST spowodowane to jest dość szybciej niż ładowanie z globalnego zakresu za pomocą LOAD_GLOBAL).

To naprawdę nie ma nic wspólnego z globalną nazwą a, która została wcześniej zdefiniowana. Ma to związek z faktem, że CPython zakłada, że ​​grasz dobrze i generuje LOAD_FAST dla odniesień do 'a', ponieważ 'a' jest przypisana do (to znaczy utworzona lokalna nazwa) wewnątrz ciała funkcji.

dla funkcji z pojedynczego dostępu nazwy i bez odpowiedniego zadania, CPython nie generuje LOAD_FAST i zamiast tego idzie i patrzy w zakresie globalnym 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 więc na to interpreter wczytuje z góry całą funkcję, aby dowiedzieć się, czy do przypisania jest używane a. 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 poradnika się następującą podano definicje funkcji:

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

szczególności wiąże nazwę f do obiektu funkcji, które posiada skompilowany kod, f.__code__, to dis prettifies dla nas.