Widziałem i użyłem funkcji zagnieżdżonych w Pythonie i pasują one do definicji zamknięcia. Dlaczego więc są nazywane nested functions
zamiast closures
?
Czy funkcje zagnieżdżone nie są zamknięciami, ponieważ nie są używane przez świat zewnętrzny?
AKTUALIZACJA: Czytałem o zamknięciach i przyszło mi do głowy myśleć o tej koncepcji w odniesieniu do Pythona. Przeszukałem i znalazłem artykuł wymieniony przez kogoś w komentarzu poniżej, ale nie mogłem całkowicie zrozumieć wyjaśnienia w tym artykule, dlatego zadaję to pytanie.
python
closures
nested-function
Srikar Appalaraju
źródło
źródło
Odpowiedzi:
Zamknięcie występuje, gdy funkcja ma dostęp do zmiennej lokalnej z zakresu obejmującego, który zakończył wykonywanie.
Po
make_printer
wywołaniu na stos jest umieszczana nowa ramka ze skompilowanym kodemprinter
funkcji jako stałej i wartościąmsg
jako lokalną. Następnie tworzy i zwraca funkcję. Ponieważ funkcjaprinter
odwołuje się domsg
zmiennej, jest utrzymywana przy życiu po jejmake_printer
zwróceniu.Więc jeśli twoje funkcje zagnieżdżone nie
to nie są zamknięcia.
Oto przykład zagnieżdżonej funkcji, która nie jest zakończeniem.
Tutaj wiążymy wartość z wartością domyślną parametru. Dzieje się tak, gdy funkcja
printer
jest tworzona, więc po powrocie nie trzeba utrzymywać odniesienia do wartościmsg
zewnętrznego do jest po prostu normalną zmienną lokalną funkcji w tym kontekście.printer
make_printer
msg
printer
źródło
self
. (W JavaScript / Python to prawie prawda.)i
] z zakresu obejmującego”. odnosi się, tj. może sprawdzać (lub zmieniać)i
wartość, nawet jeśli / kiedy zakres ten „zakończył wykonywanie”, tj. wykonanie programu przeszło do innych części kodu. Blok, w którymi
jest zdefiniowany, już nie istnieje, ale funkcje odnoszące się do niegoi
nadal mogą to zrobić. Jest to powszechnie opisywane jako „zamykanie zmienneji
”. Aby nie zajmować się określonymi zmiennymi, można je zaimplementować jako zamykanie całej ramki środowiska, w której ta zmienna jest zdefiniowana.Odpowiedź na to pytanie została już udzielona przez Aaronasterling
Jednak ktoś może być zainteresowany tym, jak zmienne są przechowywane pod maską.
Zanim przejdziesz do fragmentu:
Zamknięcia to funkcje, które dziedziczą zmienne z otaczającego środowiska. Gdy przekażesz funkcję zwrotną jako argument do innej funkcji, która wykona operacje we / wy, ta funkcja zwrotna zostanie wywołana później, a ta funkcja - niemal magicznie - zapamięta kontekst, w którym została zadeklarowana, wraz ze wszystkimi dostępnymi zmiennymi w tym kontekście.
Jeśli funkcja nie używa wolnych zmiennych, nie tworzy zamknięcia.
Jeśli istnieje inny poziom wewnętrzny, który używa wolnych zmiennych - wszystkie poprzednie poziomy zapisują środowisko leksykalne (przykład na końcu)
atrybuty funkcji
func_closure
w pythonie <3.X lub__closure__
w pythonie> 3.X zapisz wolne zmienne.Każda funkcja w pythonie ma takie atrybuty zamknięcia, ale nie zapisuje żadnej zawartości, jeśli nie ma wolnych zmiennych.
przykład: atrybutów zamknięcia, ale brak zawartości w środku, ponieważ nie ma wolnej zmiennej.
NB: DARMOWA ZMIENNA MUSI BYĆ TWORZENIE ZAMKNIĘCIA.
Wyjaśnię, używając tego samego fragmentu kodu jak powyżej:
Wszystkie funkcje Pythona mają atrybut zamknięcia, więc zbadajmy otaczające zmienne powiązane z funkcją zamknięcia.
Oto atrybut
func_closure
funkcjiprinter
closure
Atrybut zwraca parę obiektów komórkowych, które zawierają dane o zmiennych zdefiniowanych w zakresie okalającego.Pierwszy element w func_closure, którym może być None lub krotka komórek zawierających powiązania dla wolnych zmiennych funkcji i jest tylko do odczytu.
Tutaj w powyższym wyjściu możesz zobaczyć
cell_contents
, zobaczmy, co przechowuje:Tak więc, kiedy wywołaliśmy funkcję
printer()
, uzyskuje dostęp do wartości przechowywanej wewnątrzcell_contents
. W ten sposób otrzymaliśmy wynik jako „Foo!”Ponownie wyjaśnię użycie powyższego fragmentu z pewnymi zmianami:
W powyższym fragmencie nie wypisuję msg wewnątrz funkcji drukarki, więc nie tworzy żadnej wolnej zmiennej. Ponieważ nie ma wolnej zmiennej, wewnątrz zamknięcia nie będzie zawartości. Dokładnie to widzimy powyżej.
Teraz wyjaśnię inny fragment kodu, aby wyczyścić wszystko za
Free Variable
pomocąClosure
:Widzimy więc, że
func_closure
właściwość jest krotką komórek zamykających , możemy odesłać je i ich zawartość jawnie - komórka ma właściwość „cell_contents”Tutaj, kiedy zadzwoniliśmy
inn
, odniesie wszystkie zmienne wolne od zapisu, więc otrzymamyI am free variable
źródło
func_closure
jest teraz wywoływany__closure__
, podobnie jak różne innefunc_*
atrybuty.__closure_
Jest również dostępny w Python 2.6+ dla kompatybilności z Python 3.__closure__
obiekt jest zamknięciem.Python ma słabą obsługę zamykania. Aby zobaczyć, co mam na myśli, weźmy następujący przykład licznika używającego zamknięcia z JavaScript:
Zamknięcie jest dość eleganckie, ponieważ daje tak napisanym funkcjom możliwość posiadania „pamięci wewnętrznej”. Od wersji Python 2.7 nie jest to możliwe. Jeśli spróbujesz
Pojawi się błąd informujący, że x nie jest zdefiniowane. Ale jak to możliwe, jeśli inni pokazali, że można go wydrukować? Wynika to z tego, jak Python zarządza zmiennym zakresem funkcji. Chociaż funkcja wewnętrzna może odczytać zmienne funkcji zewnętrznej, nie może ich zapisać .
To naprawdę szkoda. Ale po zamknięciu tylko do odczytu można przynajmniej zaimplementować wzorzec dekoratora funkcji, dla którego Python oferuje cukier syntaktyczny.
Aktualizacja
Jak już wspomniano, istnieją sposoby radzenia sobie z ograniczeniami zakresu Pythona, a niektóre z nich ujawnię.
1. Użyj
global
słowa kluczowego (generalnie niezalecane).2. W Pythonie 3.x użyj
nonlocal
słowa kluczowego (sugerowanego przez @unutbu i @leewz)3. Zdefiniuj prostą modyfikowalną klasę
Object
i utwórz
Object scope
wewnątrz,initCounter
aby przechowywać zmiennePonieważ
scope
tak naprawdę jest tylko odniesieniem, działania podejmowane z jego polami tak naprawdę nie modyfikująscope
się, więc nie powstaje błąd.4. Alternatywnym sposobem, jak wskazał @unutbu, byłoby zdefiniowanie każdej zmiennej jako tablicy (
x = [0]
) i zmodyfikowanie jej pierwszego elementu (x[0] += 1
). Ponownie nie powstaje błąd, ponieważx
sam nie jest modyfikowany.5. Jak sugeruje @raxacoricofallapatorius, możesz zrobić
x
własnośćcounter
źródło
x = [0]
w zakresie zewnętrznym i używaćx[0] += 1
w zakresie wewnętrznym. W Python3 możesz zachować swój obecny kod i użyć nielokalnego słowa kluczowego .x
wskazuje zmienna, pozostaje dokładnie takie samo, nawet jeśli wywołujeszinc()
lub cokolwiek, i nie zapisałeś skutecznie do zmiennej.x
własnościcounter
.nonlocal
słowo kluczowe, które przypominaglobal
zmienne funkcji zewnętrznej. Umożliwi to funkcji wewnętrznej ponowne powiązanie nazwy z jej zewnętrznymi funkcjami. Myślę, że „powiązanie z nazwą” jest dokładniejsze niż „modyfikacja zmiennej”.Python 2 nie miał zamknięć - miał obejścia, które przypominały zamknięcia.
W podanych już odpowiedziach jest wiele przykładów - kopiowanie zmiennych do funkcji wewnętrznej, modyfikowanie obiektu w funkcji wewnętrznej itp.
W Pythonie 3 obsługa jest bardziej wyraźna i zwięzła:
Stosowanie:
nonlocal
Kluczowe wiąże funkcji wewnętrznej do zewnętrznej zmiennej wyraźnie wspomniano, w efekcie zamykając go. Stąd bardziej precyzyjnie „zamknięcie”.źródło
Miałem sytuację, w której potrzebowałem osobnej, ale trwałej przestrzeni nazw. Korzystałem z zajęć. Ja inaczej nie. Segregowane, ale trwałe nazwy są zamknięciami.
źródło
Daje:
To jest przykład tego, czym jest zamknięcie i jak można go użyć.
źródło
Chciałbym zaoferować inne proste porównanie między pythonem a przykładem JS, jeśli to pomoże wyjaśnić sprawę.
JS:
i wykonywanie:
Pyton:
i wykonywanie:
Powód: Jak wielu innych powiedziało powyżej, w pythonie, jeśli w wewnętrznym zakresie jest przypisane do zmiennej o tej samej nazwie, tworzone jest nowe odniesienie w wewnętrznym zakresie. Nie dotyczy to JS, chyba że jednoznacznie zadeklarujesz go
var
słowem kluczowym.źródło