Dużo czytałem o zamknięciach i myślę, że je rozumiem, ale bez zaciemniania obrazu sobie i innym mam nadzieję, że ktoś może wyjaśnić zamknięcia tak zwięźle i jasno, jak to tylko możliwe. Szukam prostego wyjaśnienia, które pomoże mi zrozumieć, gdzie i dlaczego chciałbym ich używać.
python
functional-programming
closures
znany obywatel
źródło
źródło
nonlocal
został dodany w pythonie 3, python 2.x nie miał zamkniętych zamknięć do odczytu i zapisu (tj. Można było czytać zamknięte zmienne, ale nie zmieniać ich wartości)nonlocal
słowo kluczowe w Pythonie 2 używając zmiennego obiektu,L = [0] \n def counter(): L[0] += 1; return L[0]
np. Nie możesz zmienić nazwy (powiązać ją z innym obiektem) w tym przypadku, ale możesz zmienić sam zmienny obiekt, do którego odnosi się nazwa do. Lista jest wymagana, ponieważ liczby całkowite są niezmienne w Pythonie.To proste: funkcja, która odwołuje się do zmiennych z zakresu zawierającego, potencjalnie po tym, jak przepływ kontroli opuści ten zakres. Ten ostatni fragment jest bardzo przydatny:
>>> def makeConstantAdder(x): ... constant = x ... def adder(y): ... return y + constant ... return adder ... >>> f = makeConstantAdder(12) >>> f(3) 15 >>> g = makeConstantAdder(4) >>> g(3) 7
Zauważ, że 12 i 4 „zniknęły” odpowiednio wewnątrz f i g, ta cecha sprawia, że f i g są właściwymi zamknięciami.
źródło
constant = x
; możesz po prostu zrobić toreturn y + x
w funkcji zagnieżdżonej (lub odebrać argument o nazwieconstant
) i będzie działać dobrze; argumenty są przechwytywane przez zamknięcie nie inaczej niż nieargumentowe wartości lokalne.Podoba mi się ta szorstka, zwięzła definicja :
Dodałbym
Powszechnym zastosowaniem dla zamknięć są dekoratory akceptujące parametry. Zamknięcia są powszechnym mechanizmem implementacji dla tego rodzaju „fabryki funkcji”. Często wybieram domknięcia we wzorcu strategii, gdy strategia jest modyfikowana przez dane w czasie wykonywania.
W języku, który pozwala na anonimowe definiowanie bloków - np. Ruby, C # - domknięcia mogą być używane do implementacji (do jakiego stopnia) nowych, nowych struktur kontrolnych. Brak anonimowych bloków jest jednym z ograniczeń zamknięć w Pythonie .
źródło
Szczerze mówiąc, doskonale rozumiem zamknięcia, z wyjątkiem tego, że nigdy nie byłem pewien, co dokładnie jest tym, co jest „zamknięciem” i co w nim jest „zamknięciem”. Radzę zrezygnować z szukania logiki stojącej za wyborem terminu.
W każdym razie, oto moje wyjaśnienie:
def foo(): x = 3 def bar(): print x x = 5 return bar bar = foo() bar() # print 5
Kluczową ideą jest tutaj to, że obiekt funkcji zwrócony z foo zachowuje punkt zaczepienia do lokalnej zmiennej „x”, mimo że „x” wyszedł poza zakres i powinien być zlikwidowany. Ten punkt zaczepienia jest związany z samą zmienną, a nie tylko wartością, która miała zmienna w tym czasie, więc po wywołaniu bar wypisuje 5, a nie 3.
Wyjaśnij również, że Python 2.x ma ograniczone zamknięcie: nie ma sposobu, abym mógł zmodyfikować „x” wewnątrz „bar”, ponieważ wpisanie „x = bla” zadeklarowałoby lokalny „x” w pasku, a nie przypisany do „x” w foo . Jest to efekt uboczny deklaracji przypisania = w Pythonie. Aby obejść ten problem, Python 3.0 wprowadza nielokalne słowo kluczowe:
def foo(): x = 3 def bar(): print x def ack(): nonlocal x x = 7 x = 5 return (bar, ack) bar, ack = foo() ack() # modify x of the call to foo bar() # print 7
źródło
Nigdy nie słyszałem, aby transakcje były używane w tym samym kontekście, co wyjaśnianie, czym jest zamknięcie i tak naprawdę nie ma tu żadnej semantyki transakcji.
Nazywa się to zamknięciem, ponieważ „zamyka” zewnętrzną zmienną (stałą) - tj. Nie jest tylko funkcją, ale obudową środowiska, w którym została utworzona.
W poniższym przykładzie wywołanie zamknięcia g po zmianie x zmieni również wartość x w obrębie g, ponieważ g zamyka się nad x:
x = 0 def f(): def g(): return x * 2 return g closure = f() print(closure()) # 0 x = 2 print(closure()) # 4
źródło
g()
oblicza,x * 2
ale nic nie zwraca. Tak powinno byćreturn x * 2
. +1 mimo wszystko za wyjaśnienie słowa „zamknięcie”.Oto typowy przypadek użycia zamknięć - wywołania zwrotne elementów GUI (byłoby to alternatywa dla podklasy klasy przycisku). Na przykład można skonstruować funkcję, która będzie wywoływana w odpowiedzi na naciśnięcie przycisku i „zamyka” odpowiednie zmienne w zakresie nadrzędnym, które są niezbędne do przetworzenia kliknięcia. W ten sposób możesz połączyć dość skomplikowane interfejsy z tej samej funkcji inicjalizacyjnej, budując wszystkie zależności w zamknięciu.
źródło
W Pythonie zamknięcie jest instancją funkcji, do której są niezmiennie przypisane zmienne.
W rzeczywistości model danych wyjaśnia to w swoim opisie
__closure__
atrybutu funkcji :Aby to zademonstrować:
def enclosure(foo): def closure(bar): print(foo, bar) return closure closure_instance = enclosure('foo')
Oczywiście wiemy, że mamy teraz wskazaną funkcję z nazwy zmiennej
closure_instance
. Pozornie, jeśli wywołasz go za pomocą obiektu,bar
powinien wypisać łańcuch'foo'
i niezależnie od jego reprezentacjibar
.W rzeczywistości ciąg „foo” jest powiązany z wystąpieniem funkcji i możemy go bezpośrednio przeczytać tutaj, uzyskując dostęp do
cell_contents
atrybutu pierwszej (i jedynej) komórki w krotce__closure__
atrybutu:>>> closure_instance.__closure__[0].cell_contents 'foo'
Na marginesie, obiekty komórek są opisane w dokumentacji C API:
Możemy zademonstrować użycie naszego zamknięcia, zauważając, że
'foo'
utknęło w funkcji i się nie zmienia:>>> closure_instance('bar') foo bar >>> closure_instance('baz') foo baz >>> closure_instance('quux') foo quux
I nic nie może tego zmienić:
>>> closure_instance.__closure__ = None Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: readonly attribute
Funkcje częściowe
Podany przykład wykorzystuje zamknięcie jako funkcję częściową, ale jeśli jest to nasz jedyny cel, ten sam cel można osiągnąć za pomocą
functools.partial
>>> from __future__ import print_function # use this if you're in Python 2. >>> partial_function = functools.partial(print, 'foo') >>> partial_function('bar') foo bar >>> partial_function('baz') foo baz >>> partial_function('quux') foo quux
Istnieją również bardziej skomplikowane domknięcia, które nie pasowałyby do przykładu funkcji częściowej, i pokażę je dalej, gdy pozwoli na to czas.
źródło
# A Closure is a function object that remembers values in enclosing scopes even if they are not present in memory. # Defining a closure # This is an outer function. def outer_function(message): # This is an inner nested function. def inner_function(): print(message) return inner_function # Now lets call the outer function and return value bound to name 'temp' temp = outer_function("Hello") # On calling temp, 'message' will be still be remembered although we had finished executing outer_function() temp() # Technique by which some data('message') that remembers values in enclosing scopes # even if they are not present in memory is called closures # Output: Hello
Kryteria, które muszą spełnić Zamknięcia to:
# Example 2 def make_multiplier_of(n): # Outer function def multiplier(x): # Inner nested function return x * n return multiplier # Multiplier of 3 times3 = make_multiplier_of(3) # Multiplier of 5 times5 = make_multiplier_of(5) print(times5(3)) # 15 print(times3(2)) # 6
źródło
Oto przykład zamknięć w Pythonie3
def closure(x): def counter(): nonlocal x x += 1 return x return counter; counter1 = closure(100); counter2 = closure(200); print("i from closure 1 " + str(counter1())) print("i from closure 1 " + str(counter1())) print("i from closure 2 " + str(counter2())) print("i from closure 1 " + str(counter1())) print("i from closure 1 " + str(counter1())) print("i from closure 1 " + str(counter1())) print("i from closure 2 " + str(counter2())) # result i from closure 1 101 i from closure 1 102 i from closure 2 201 i from closure 1 103 i from closure 1 104 i from closure 1 105 i from closure 2 202
źródło
Dla mnie „domknięcia” to funkcje, które są w stanie zapamiętać środowisko, w którym zostały stworzone. Ta funkcjonalność pozwala na użycie zmiennych lub metod w domknięciu, których w inny sposób nie byłbyś w stanie użyć, ponieważ już nie istnieją lub są poza zasięgiem ze względu na zakres. Spójrzmy na ten kod w Rubim:
def makefunction (x) def multiply (a,b) puts a*b end return lambda {|n| multiply(n,x)} # => returning a closure end func = makefunction(2) # => we capture the closure func.call(6) # => Result equal "12"
działa nawet wtedy, gdy zarówno metoda „multiply”, jak i zmienna „x” już nie istnieją. Wszystko ze względu na zapięcie do zapamiętania.
źródło
wszyscy używaliśmy dekoratorów w Pythonie. Są fajnymi przykładami pokazującymi, czym są funkcje zamykające w Pythonie.
class Test(): def decorator(func): def wrapper(*args): b = args[1] + 5 return func(b) return wrapper @decorator def foo(val): print val + 2 obj = Test() obj.foo(5)
tutaj końcowa wartość to 12
Tutaj funkcja opakowująca ma dostęp do obiektu func, ponieważ opakowanie jest „zamknięciem leksykalnym”, ma dostęp do jego atrybutów nadrzędnych. Dlatego jest w stanie uzyskać dostęp do obiektu funkcyjnego.
źródło
Chciałbym podzielić się moim przykładem i wyjaśnieniem na temat zamknięć. Zrobiłem przykład w Pythonie i dwie figury, aby zademonstrować stany stosu.
def maker(a, b, n): margin_top = 2 padding = 4 def message(msg): print('\n’ * margin_top, a * n, ' ‘ * padding, msg, ' ‘ * padding, b * n) return message f = maker('*', '#', 5) g = maker('', '♥’, 3) … f('hello') g(‘good bye!')
Wynik tego kodu wyglądałby następująco:
***** hello ##### good bye! ♥♥♥
Oto dwie figury przedstawiające stosy i zamknięcie dołączone do obiektu funkcyjnego.
gdy funkcja jest zwracana przez producenta
kiedy funkcja zostanie wywołana później
Gdy funkcja jest wywoływana za pomocą parametru lub zmiennej nielokalnej, kod wymaga lokalnych powiązań zmiennych, takich jak margin_top, dopełnienie, a także a, b, n. Aby kod funkcji działał, ramka stosu funkcji kreatora, która zniknęła dawno temu, powinna być dostępna, a kopia zapasowa znajduje się w domknięciu, które możemy znaleźć wraz z obiektem funkcji wiadomości.
źródło
Najlepszym wyjaśnieniem zamknięcia, jakie kiedykolwiek widziałem, było wyjaśnienie mechanizmu. To wyglądało mniej więcej tak:
Wyobraź sobie stos programu jako zdegenerowane drzewo, w którym każdy węzeł ma tylko jedno dziecko, a węzeł z jednym liściem jest kontekstem aktualnie wykonywanej procedury.
Teraz zwolnij ograniczenie, że każdy węzeł może mieć tylko jedno dziecko.
Jeśli to zrobisz, możesz mieć konstrukcję ('yield'), która może powrócić z procedury bez odrzucania lokalnego kontekstu (tj. Nie zdejmuje jej ze stosu po powrocie). Następnym razem, gdy procedura jest wywoływana, wywołanie podnosi starą ramkę stosu (drzewa) i kontynuuje wykonywanie od miejsca, w którym zostało przerwane.
źródło