Zakres klas i lista, zestaw lub słownik, a także wyrażenia generatora nie mieszają się.
Dlaczego; lub oficjalne słowo na ten temat
W Pythonie 3 wyrażeniom listowym nadano własny odpowiedni zakres (lokalną przestrzeń nazw), aby zapobiec przedostawaniu się ich zmiennych lokalnych do otaczającego zakresu (zobacz Python list comprinding nazwy nawet po zakresie zrozumienia. Czy to prawda? ). To świetnie, gdy używasz takiego rozumienia list w module lub w funkcji, ale w klasach zakres jest trochę, hm, dziwny .
Jest to udokumentowane w pep 227 :
Nazwy w zakresie klas nie są dostępne. Nazwy są rozwiązywane w najbardziej wewnętrznym obejmującym zakresie funkcji. Jeśli definicja klasy występuje w łańcuchu zagnieżdżonych zasięgów, proces rozstrzygania pomija definicje klas.
aw class
dokumentacji złożonej :
Zestaw klasy jest następnie wykonywany w nowej ramce wykonawczej (zob. Sekcja Nazewnictwo i powiązanie ), przy użyciu nowo utworzonej lokalnej przestrzeni nazw i oryginalnej globalnej przestrzeni nazw. (Zwykle pakiet zawiera tylko definicje funkcji). Kiedy zestaw klasy kończy wykonywanie, ramka wykonania jest odrzucana, ale lokalna przestrzeń nazw jest zapisywana . [4] Obiekt klasy jest następnie tworzony przy użyciu listy dziedziczenia dla klas podstawowych i zapisanej lokalnej przestrzeni nazw dla słownika atrybutów.
Podkreśl moje; ramą wykonania jest tymczasowy zakres.
Ponieważ zakres jest zmieniany jako atrybuty obiektu klasy, zezwolenie na użycie go jako zakresu nielokalnego również prowadzi do niezdefiniowanego zachowania; co by się stało, gdyby metoda klasy określana x
jako zagnieżdżona zmienna zasięgu Foo.x
, na przykład również manipulowała ? Co ważniejsze, co to oznaczałoby dla podklas Foo
? Python musi inaczej traktować zakres klasy, ponieważ bardzo różni się on od zakresu funkcji.
Wreszcie, połączona sekcja Nazewnictwa i powiązań w dokumentacji modelu wykonywania wymienia wyraźnie zakresy klas:
Zakres nazw zdefiniowanych w bloku klasy jest ograniczony do bloku klasy; nie rozciąga się na bloki kodu metod - obejmuje to wyrażenia i wyrażenia generatora, ponieważ są one implementowane przy użyciu zakresu funkcji. Oznacza to, że następujące działania zakończą się niepowodzeniem:
class A:
a = 42
b = list(a + i for i in range(10))
Podsumowując: nie można uzyskać dostępu do zakresu klasy z funkcji, wyrażeń listowych lub wyrażeń generatora zawartych w tym zakresie; działają tak, jakby ten zakres nie istniał. W Pythonie 2 listy składane zostały zaimplementowane za pomocą skrótu, ale w Pythonie 3 otrzymały swój własny zakres funkcji (tak, jak powinny były mieć przez cały czas) i dlatego Twój przykład się psuje. Inne typy rozumienia mają swój własny zakres niezależnie od wersji Pythona, więc podobny przykład ze zrozumieniem zestawu lub dyktu nie działa w Pythonie 2.
# Same error, in Python 2 or 3
y = {x: x for i in range(1)}
(Mały) wyjątek; lub dlaczego jedna część może nadal działać
Istnieje jedna część wyrażenia zrozumienia lub wyrażenia generatora, która jest wykonywana w otaczającym zakresie, niezależnie od wersji języka Python. To byłby wyraz najbardziej zewnętrznej iterowalności. W twoim przykładzie jest to range(1)
:
y = [x for i in range(1)]
# ^^^^^^^^
Zatem użycie x
w tym wyrażeniu nie spowodowałoby błędu:
# Runs fine
y = [i for i in range(x)]
Dotyczy to tylko najbardziej zewnętrznych iterowalnych; jeśli zrozumienie ma wiele for
klauzul, iterowalne dla for
klauzul wewnętrznych są oceniane w zakresie zrozumienia:
# NameError
y = [i for i in range(1) for j in range(x)]
Ta decyzja projektowa została podjęta, aby zgłosić błąd w czasie tworzenia genexp zamiast czasu iteracji, gdy tworzenie najbardziej zewnętrznej iterowalnej ekspresji generatora zgłasza błąd lub gdy najbardziej zewnętrzna iterowalna okazuje się nie być iterowalna. Zrozumienia dzielą to zachowanie dla spójności.
Patrząc pod maskę; lub o wiele więcej szczegółów niż kiedykolwiek chciałeś
Możesz zobaczyć to wszystko w akcji za pomocą dis
modułu . W poniższych przykładach używam języka Python 3.3, ponieważ dodaje on nazwy kwalifikowane, które dokładnie identyfikują obiekty kodu, które chcemy sprawdzić. Wytworzony kod bajtowy jest poza tym funkcjonalnie identyczny z Pythonem 3.2.
Aby utworzyć klasę, Python w zasadzie bierze cały zestaw, który tworzy ciało klasy (więc wszystko jest wcięte o jeden poziom głębiej niż class <name>:
linia) i wykonuje to tak, jakby to była funkcja:
>>> import dis
>>> def foo():
... class Foo:
... x = 5
... y = [x for i in range(1)]
... return Foo
...
>>> dis.dis(foo)
2 0 LOAD_BUILD_CLASS
1 LOAD_CONST 1 (<code object Foo at 0x10a436030, file "<stdin>", line 2>)
4 LOAD_CONST 2 ('Foo')
7 MAKE_FUNCTION 0
10 LOAD_CONST 2 ('Foo')
13 CALL_FUNCTION 2 (2 positional, 0 keyword pair)
16 STORE_FAST 0 (Foo)
5 19 LOAD_FAST 0 (Foo)
22 RETURN_VALUE
Pierwsza z LOAD_CONST
nich ładuje obiekt kodu dla Foo
treści klasy, następnie przekształca go w funkcję i wywołuje. Wynikiem tego połączenia jest następnie wykorzystywany do tworzenia nazw klasy, jej __dict__
. Na razie w porządku.
Należy tu zauważyć, że kod bajtowy zawiera zagnieżdżony obiekt kodu; w Pythonie definicje klas, funkcje, wyrażenia i generatory są reprezentowane jako obiekty kodu, które zawierają nie tylko kod bajtowy, ale także struktury reprezentujące zmienne lokalne, stałe, zmienne pobrane z globalnych i zmienne pobrane z zakresu zagnieżdżonego. Skompilowany kod bajtowy odnosi się do tych struktur, a interpreter Pythona wie, jak uzyskać dostęp do tych, które mają przedstawione kody bajtowe.
Ważną rzeczą do zapamiętania jest to, że Python tworzy te struktury w czasie kompilacji; class
apartament jest obiektem kod ( <code object Foo at 0x10a436030, file "<stdin>", line 2>
), która jest już skompilowany.
Przyjrzyjmy się temu obiektowi kodu, który tworzy samą treść klasy; obiekty kodu mają co_consts
strukturę:
>>> foo.__code__.co_consts
(None, <code object Foo at 0x10a436030, file "<stdin>", line 2>, 'Foo')
>>> dis.dis(foo.__code__.co_consts[1])
2 0 LOAD_FAST 0 (__locals__)
3 STORE_LOCALS
4 LOAD_NAME 0 (__name__)
7 STORE_NAME 1 (__module__)
10 LOAD_CONST 0 ('foo.<locals>.Foo')
13 STORE_NAME 2 (__qualname__)
3 16 LOAD_CONST 1 (5)
19 STORE_NAME 3 (x)
4 22 LOAD_CONST 2 (<code object <listcomp> at 0x10a385420, file "<stdin>", line 4>)
25 LOAD_CONST 3 ('foo.<locals>.Foo.<listcomp>')
28 MAKE_FUNCTION 0
31 LOAD_NAME 4 (range)
34 LOAD_CONST 4 (1)
37 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
40 GET_ITER
41 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
44 STORE_NAME 5 (y)
47 LOAD_CONST 5 (None)
50 RETURN_VALUE
Powyższy kod bajtowy tworzy treść klasy. Funkcja jest wykonywana, a wynikowa locals()
przestrzeń nazw, zawierająca x
i y
jest używana do tworzenia klasy (z wyjątkiem tego, że nie działa, ponieważ x
nie jest zdefiniowana jako globalna). Zauważ, że po zapisaniu 5
w x
, ładuje inny obiekt kodu; to jest rozumienie listy; jest opakowany w obiekt funkcji, tak jak było w treści klasy; utworzona funkcja przyjmuje argument pozycyjny, range(1)
iterowalny do użycia w swoim kodzie pętli, rzutowany na iterator. Jak pokazano w kodzie bajtowym, range(1)
jest oceniany w zakresie klasy.
Z tego widać, że jedyną różnicą między obiektem kodu dla funkcji lub generatora a obiektem kodu dla zrozumienia jest to, że ten ostatni jest wykonywany natychmiast po wykonaniu obiektu kodu nadrzędnego; kod bajtowy po prostu tworzy funkcję w locie i wykonuje ją w kilku małych krokach.
Python 2.x zamiast tego używa wbudowanego kodu bajtowego, tutaj jest wyjście z Pythona 2.7:
2 0 LOAD_NAME 0 (__name__)
3 STORE_NAME 1 (__module__)
3 6 LOAD_CONST 0 (5)
9 STORE_NAME 2 (x)
4 12 BUILD_LIST 0
15 LOAD_NAME 3 (range)
18 LOAD_CONST 1 (1)
21 CALL_FUNCTION 1
24 GET_ITER
>> 25 FOR_ITER 12 (to 40)
28 STORE_NAME 4 (i)
31 LOAD_NAME 2 (x)
34 LIST_APPEND 2
37 JUMP_ABSOLUTE 25
>> 40 STORE_NAME 5 (y)
43 LOAD_LOCALS
44 RETURN_VALUE
Żaden obiekt kodu nie jest ładowany, zamiast tego FOR_ITER
uruchamiana jest pętla. Tak więc w Pythonie 3.x generator list otrzymał własny, właściwy obiekt kodu, co oznacza, że ma swój własny zakres.
Jednak interpretacja została skompilowana wraz z resztą kodu źródłowego w języku Python, gdy moduł lub skrypt został po raz pierwszy załadowany przez interpreter, a kompilator nie uważa zestawu klas za prawidłowy zakres. Wszelkie zmienne, do których odwołuje się lista, muszą odnosić się rekurencyjnie do zakresu otaczającego definicję klasy. Jeśli zmienna nie została znaleziona przez kompilator, oznacza ją jako globalną. Demontaż obiektu kodu zrozumienia listy pokazuje, że x
faktycznie jest załadowany jako globalny:
>>> foo.__code__.co_consts[1].co_consts
('foo.<locals>.Foo', 5, <code object <listcomp> at 0x10a385420, file "<stdin>", line 4>, 'foo.<locals>.Foo.<listcomp>', 1, None)
>>> dis.dis(foo.__code__.co_consts[1].co_consts[2])
4 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (i)
12 LOAD_GLOBAL 0 (x)
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
Ten fragment kodu bajtowego ładuje pierwszy przekazany argument ( range(1)
iterator) i podobnie jak wersja Python 2.x używa go FOR_ITER
do zapętlenia nad nim i utworzenia wyjścia.
Gdybyśmy zamiast tego zdefiniowali x
w foo
funkcji, x
byłaby to zmienna komórki (komórki odnoszą się do zagnieżdżonych zakresów):
>>> def foo():
... x = 2
... class Foo:
... x = 5
... y = [x for i in range(1)]
... return Foo
...
>>> dis.dis(foo.__code__.co_consts[2].co_consts[2])
5 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (i)
12 LOAD_DEREF 0 (x)
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
LOAD_DEREF
Pośrednio załadować x
z obiektów komórkowych obiekt Kod:
>>> foo.__code__.co_cellvars # foo function `x`
('x',)
>>> foo.__code__.co_consts[2].co_cellvars # Foo class, no cell variables
()
>>> foo.__code__.co_consts[2].co_consts[2].co_freevars # Refers to `x` in foo
('x',)
>>> foo().y
[2]
Rzeczywiste odwołanie wyszukuje wartość z bieżących struktur danych ramki, które zostały zainicjowane z .__closure__
atrybutu obiektu funkcji . Ponieważ funkcja utworzona dla obiektu kodu zrozumienia jest ponownie odrzucana, nie możemy sprawdzić zamknięcia tej funkcji. Aby zobaczyć zamknięcie w akcji, musielibyśmy zamiast tego sprawdzić zagnieżdżoną funkcję:
>>> def spam(x):
... def eggs():
... return x
... return eggs
...
>>> spam(1).__code__.co_freevars
('x',)
>>> spam(1)()
1
>>> spam(1).__closure__
>>> spam(1).__closure__[0].cell_contents
1
>>> spam(5).__closure__[0].cell_contents
5
Podsumowując:
- Listy składane otrzymują własne obiekty kodu w Pythonie 3 i nie ma różnicy między obiektami kodu dla funkcji, generatorów lub wyrażeń; obiekty kodu zrozumienia są opakowane w tymczasowy obiekt funkcji i natychmiast wywołane.
- Obiekty kodu są tworzone w czasie kompilacji, a wszelkie zmienne nielokalne są oznaczane jako zmienne globalne lub wolne, na podstawie zagnieżdżonych zakresów kodu. Treść klasy nie jest uważana za zakres wyszukiwania tych zmiennych.
- Podczas wykonywania kodu Python musi tylko zajrzeć do globalnych lub zamknięcia aktualnie wykonywanego obiektu. Ponieważ kompilator nie uwzględnił treści klasy jako zakresu, tymczasowa przestrzeń nazw funkcji nie jest uwzględniana.
Obejście; lub co z tym zrobić
Jeśli było stworzyć wyraźny zakres dla x
zmiennej, podobnie jak w funkcji, to można używać zmiennych klasy zakres wyrażeń listowych:
>>> class Foo:
... x = 5
... def y(x):
... return [x for i in range(1)]
... y = y(x)
...
>>> Foo.y
[5]
Funkcję „tymczasową” y
można wywołać bezpośrednio; zastępujemy go, gdy robimy z jego wartością zwracaną. Jego zakres jest brany pod uwagę przy rozwiązywaniu x
:
>>> foo.__code__.co_consts[1].co_consts[2]
<code object y at 0x10a5df5d0, file "<stdin>", line 4>
>>> foo.__code__.co_consts[1].co_consts[2].co_cellvars
('x',)
Oczywiście ludzie czytający twój kod podrapią się trochę po głowie; możesz zamieścić tam duży gruby komentarz wyjaśniający, dlaczego to robisz.
Najlepszym obejściem jest użycie __init__
do utworzenia zmiennej instancji:
def __init__(self):
self.y = [self.x for i in range(1)]
i unikaj drapania się po głowie i pytań, aby się wytłumaczyć. Dla twojego konkretnego przykładu, nie zapisałbym nawet tego namedtuple
na zajęciach; użyj wyjścia bezpośrednio (w ogóle nie przechowuj wygenerowanej klasy) lub użyj globalnego:
from collections import namedtuple
State = namedtuple('State', ['name', 'capital'])
class StateDatabase:
db = [State(*args) for args in [
('Alabama', 'Montgomery'),
('Alaska', 'Juneau'),
# ...
]]
NameError: global name 'x' is not defined
na Pythona 3.2 i 3.3, czego bym się spodziewał.