W jaki sposób uzyskujesz dostęp do innych zmiennych klasy z listy złożonej w definicji klasy? Poniższe działa w Pythonie 2, ale kończy się niepowodzeniem w Pythonie 3:
class Foo:
x = 5
y = [x for i in range(1)]
Python 3.2 podaje błąd:
NameError: global name 'x' is not defined
Próbowanie Foo.x
też nie działa. Jakieś pomysły, jak to zrobić w Pythonie 3?
Nieco bardziej skomplikowany motywujący przykład:
from collections import namedtuple
class StateDatabase:
State = namedtuple('State', ['name', 'capital'])
db = [State(*args) for args in [
['Alabama', 'Montgomery'],
['Alaska', 'Juneau'],
# ...
]]
W tym przykładzie apply()
byłoby to przyzwoite obejście, ale niestety zostało usunięte z Pythona 3.
python
python-3.x
scope
list-comprehension
python-internals
Mark Lodato
źródło
źródło
NameError: global name 'x' is not defined
na Pythona 3.2 i 3.3, czego bym się spodziewał.Odpowiedzi:
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 :
aw
class
dokumentacji złożonej :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ęguFoo.x
, na przykład również manipulowała ? Co ważniejsze, co to oznaczałoby dla podklasFoo
? 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:
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.
(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)
:Zatem użycie
x
w tym wyrażeniu nie spowodowałoby błędu:Dotyczy to tylko najbardziej zewnętrznych iterowalnych; jeśli zrozumienie ma wiele
for
klauzul, iterowalne dlafor
klauzul wewnętrznych są oceniane w zakresie zrozumienia: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:Pierwsza z
LOAD_CONST
nich ładuje obiekt kodu dlaFoo
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ę:Powyższy kod bajtowy tworzy treść klasy. Funkcja jest wykonywana, a wynikowa
locals()
przestrzeń nazw, zawierającax
iy
jest używana do tworzenia klasy (z wyjątkiem tego, że nie działa, ponieważx
nie jest zdefiniowana jako globalna). Zauważ, że po zapisaniu5
wx
, ł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:
Ż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:Ten fragment kodu bajtowego ładuje pierwszy przekazany argument (
range(1)
iterator) i podobnie jak wersja Python 2.x używa goFOR_ITER
do zapętlenia nad nim i utworzenia wyjścia.Gdybyśmy zamiast tego zdefiniowali
x
wfoo
funkcji,x
byłaby to zmienna komórki (komórki odnoszą się do zagnieżdżonych zakresów):LOAD_DEREF
Pośrednio załadowaćx
z obiektów komórkowych obiekt Kod: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ę:Podsumowując:
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: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ązywaniux
: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: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:źródło
y = (lambda x=x: [x for i in range(1)])()
lambda
to tylko anonimowe funkcje.Moim zdaniem jest to wada w Pythonie 3. Mam nadzieję, że to zmienią.
Old Way (działa w 2.7, rzuca
NameError: name 'x' is not defined
w 3+):UWAGA: samo określenie zakresu
A.x
nie rozwiązałoby problemuNew Way (działa w 3+):
Ponieważ składnia jest tak brzydka, po prostu inicjalizuję wszystkie moje zmienne klasy w konstruktorze
źródło
def
do utworzenia funkcji).python -c "import IPython;IPython.embed()"
. Uruchom IPython bezpośrednio za pomocą say,ipython
a problem zniknie.Przyjęta odpowiedź dostarcza doskonałych informacji, ale wydaje się, że jest tu kilka innych zmarszczek - różnice między zrozumieniem listy a wyrażeniami generatora. Demo, z którym się bawiłem:
źródło
To jest błąd w Pythonie. Rozumienia są reklamowane jako równoważne pętlom for, ale nie jest to prawdą w klasach. Przynajmniej do Pythona 3.6.6, w zrozumieniu używanym w klasie, tylko jedna zmienna spoza zrozumienia jest dostępna wewnątrz zrozumienia i musi być używana jako najbardziej zewnętrzny iterator. W funkcji to ograniczenie zakresu nie ma zastosowania.
Aby zilustrować, dlaczego jest to błąd, wróćmy do oryginalnego przykładu. To się nie udaje:
Ale to działa:
Ograniczenie podano na końcu tej sekcji w przewodniku informacyjnym.
źródło
Ponieważ najbardziej zewnętrzny iterator jest oceniany w otaczającym zakresie, możemy użyć
zip
razem z,itertools.repeat
aby przenieść zależności do zakresu zrozumienia:Można również użyć zagnieżdżonych
for
pętli w zrozumieniu i uwzględnić zależności w najbardziej zewnętrznej iterowalnej:Konkretny przykład PO:
źródło