Dobra, wytrzymaj ze mną, wiem, że to będzie wyglądać strasznie zagmatwane, ale proszę, pomóż mi zrozumieć, co się dzieje.
from functools import partial
class Cage(object):
def __init__(self, animal):
self.animal = animal
def gotimes(do_the_petting):
do_the_petting()
def get_petters():
for animal in ['cow', 'dog', 'cat']:
cage = Cage(animal)
def pet_function():
print "Mary pets the " + cage.animal + "."
yield (animal, partial(gotimes, pet_function))
funs = list(get_petters())
for name, f in funs:
print name + ":",
f()
Daje:
cow: Mary pets the cat.
dog: Mary pets the cat.
cat: Mary pets the cat.
Więc w zasadzie, dlaczego nie otrzymuję trzech różnych zwierząt? Czy nie jest cage
„spakowany” w lokalnym zakresie zagnieżdżonej funkcji? Jeśli nie, w jaki sposób wywołanie funkcji zagnieżdżonej wyszukuje zmienne lokalne?
Wiem, że napotkanie tego rodzaju problemów zwykle oznacza, że „robi się to źle”, ale chciałbym zrozumieć, co się dzieje.
for animal in ['cat', 'dog', 'cow']
... Jestem pewien, że ktoś przyjdzie i to wyjaśni - to jeden z tych Pythona gotcha :)Odpowiedzi:
Zagnieżdżona funkcja wyszukuje zmienne z zakresu nadrzędnego po wykonaniu, a nie po zdefiniowaniu.
Treść funkcji jest kompilowana, a zmienne „wolne” (niezdefiniowane w samej funkcji przez przypisanie) są weryfikowane, a następnie wiązane jako komórki zamykające z funkcją, a kod używa indeksu do odwoływania się do każdej komórki.
pet_function
ma zatem jedną wolną zmienną (cage
), do której następnie odwołuje się komórka zamykająca o indeksie 0. Samo zamknięcie wskazuje na zmienną lokalnącage
wget_petters
funkcji.Kiedy faktycznie wywołujesz funkcję, to zamknięcie jest następnie używane do sprawdzania wartości
cage
w otaczającym zakresie w momencie wywołania funkcji . Tu tkwi problem. W momencie wywoływania funkcjiget_petters
funkcja jest już zakończona obliczaniem swoich wyników.cage
Zmiennej lokalnej w pewnym momencie, że wykonanie zostało przypisane każdej z'cow'
,'dog'
i'cat'
strun, ale na końcu funkcji,cage
to zawiera ostatnią wartość'cat'
. Tak więc, kiedy wywołujesz każdą z dynamicznie zwracanych funkcji, otrzymujesz'cat'
wydrukowaną wartość .Rozwiązaniem jest nie poleganie na zamknięciach. Zamiast tego można użyć funkcji częściowej , utworzyć nowy zakres funkcji lub powiązać zmienną jako wartość domyślną parametru słowa kluczowego .
Przykład funkcji częściowej, używając
functools.partial()
:Tworzenie nowego przykładu zakresu:
Powiązanie zmiennej jako domyślnej wartości parametru słowa kluczowego:
Nie ma potrzeby definiowania
scoped_cage
funkcji w pętli, kompilacja odbywa się tylko raz, a nie przy każdej iteracji pętli.źródło
Rozumiem, że klatka jest szukana w przestrzeni nazw funkcji nadrzędnej, gdy faktycznie wywoływana jest funkcja pet_function, a nie wcześniej.
Więc kiedy to zrobisz
Generujesz 3 funkcje, które odnajdą ostatnio utworzoną klatkę.
Jeśli zamienisz ostatnią pętlę na:
Otrzymasz:
źródło
Wynika to z następujących
po iteracji wartość
i
jest leniwie przechowywana jako wartość końcowa.Jako generator funkcja działałaby (tj. Wypisałaby każdą wartość po kolei), ale podczas przekształcania w listę przechodzi przez generator , stąd wszystkie wywołania funkcji
cage
(cage.animal
) zwracają koty.źródło
Uprośćmy pytanie. Definiować:
Następnie, tak jak w pytaniu, otrzymujemy:
Ale jeśli unikniemy tworzenia
list()
pierwszego:Co się dzieje? Dlaczego ta subtelna różnica całkowicie zmienia nasze wyniki?
Jeśli się przyjrzymy
list(get_petters())
, ze zmieniających się adresów pamięci jasno wynika, że rzeczywiście dajemy trzy różne funkcje:Jednak spójrz na elementy,
cell
które te funkcje są powiązane:W przypadku obu pętli
cell
obiekt pozostaje taki sam przez wszystkie iteracje. Jednak, zgodnie z oczekiwaniami, specyfika, dostr
której się odnosi, różni się w drugiej pętli.cell
Obiektu odnosi się doanimal
, który tworzy się, gdyget_petters()
jest tzw. Jednakanimal
zmieniastr
obiekt, do którego się odnosi, gdy działa funkcja generatora .W pierwszej pętli podczas każdej iteracji tworzymy wszystkie
f
s, ale wywołujemy je dopiero poget_petters()
całkowitym wyczerpaniu generatora i utworzeniu jednejlist
z funkcji.W drugiej pętli, podczas każdej iteracji, zatrzymujemy
get_petters()
generator i dzwonimyf
po każdej przerwie. W ten sposób otrzymujemy wartośćanimal
w tym momencie, w którym funkcja generatora jest wstrzymana.Jak @Claudiu odpowiada na podobne pytanie :
źródło