Czy możesz wyjaśnić zamknięcia (jak odnoszą się do Pythona)?

Czytałem dużo o zamknięciach i myślę, że je rozumiem, ale nie zaciemniając obrazu dla siebie i innych, mam nadzieję, że ktoś wyjaśni zamknięcia tak zwięźle i jasno, jak to możliwe. Szukam prostego wyjaśnienia, które może mi pomóc zrozumieć, gdzie i Dlaczego chciałbym z nich korzystać.

Author: Honest Abe, 2008-08-17

13 answers

Zamknięcie na zamknięciach

Obiekty są danymi z metodami dołączone, zamknięcia są funkcjami z dane dołączone.

def make_counter():
    i = 0
    def counter(): # counter() is a closure
        nonlocal i
        i += 1
        return i
    return counter

c1 = make_counter()
c2 = make_counter()

print (c1(), c1(), c2(), c2())
# -> 1 2 1 2
 77
Author: jfs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-25 06:46:26

To proste: funkcja, która odwołuje się do zmiennych z zakresu zawierającego, potencjalnie po opuszczeniu tego zakresu przez flow-of-control. Ten ostatni bit 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ą zamknięciami właściwymi.

 42
Author: Anders Eurenius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-17 19:32:33

Podoba mi się ta szorstka, zwięzła definicja :

Funkcja, która może odnosić się do środowisk, które nie są już aktywne.

Dodałbym

Zamknięcie pozwala powiązać zmienne w funkcję bez przekazywania ich jako parametrów .

Dekoratory, które akceptują parametry, są powszechnym zastosowaniem dla zamknięć. Zamknięcia są powszechnym mechanizmem implementacji tego rodzaju "fabryki funkcyjnej". Często korzystam z zamknięć we wzorze strategii , gdy strategia jest modyfikowana przez dane w czasie wykonywania.

W języku pozwalającym na anonimowe definiowanie bloków-np. Ruby, C# -- closures mogą być użyte do implementacji (do jakiej ilości) nowych struktur sterujących. Brak anonimowych bloków jest jednym z ograniczeń zamknięć w Pythonie .

 14
Author: ESV,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-17 21:11:20

Szczerze mówiąc, doskonale rozumiem zamknięcia, z wyjątkiem tego, że nigdy nie byłem jasny co dokładnie jest tym, co jest "zamknięciem" i co jest w tym tak "zamknięciem". Polecam zrezygnować z szukania jakiejkolwiek 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 hook do lokalnego var 'x', mimo że 'x' wyszedł poza zakres i powinien być nieaktywny. Ten hak jest do var sama, nie tylko wartość, którą var miał w tym czasie, więc po wywołaniu bar wyświetla 5, a nie 3.

Również być jasne, że Python 2.x ma ograniczone zamknięcie: nie ma możliwości modyfikacji 'x' Wewnątrz 'bar', ponieważ zapis 'x = bla' deklarowałby lokalne 'x' W bar, a nie przypisywał do 'x' foo. Jest to efekt uboczny deklaracji assignment = Pythona. Aby to obejść, Python 3.0 wprowadza słowo kluczowe nonlocal:

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
 13
Author: Jegschemesch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-12 01:42:13

Nigdy nie słyszałem, aby transakcje były używane w tym samym kontekście, co wyjaśnienie, czym jest zamknięcie i naprawdę nie ma tu żadnej semantyki transakcji.

Nazywa się zamknięciem, ponieważ "zamyka" zewnętrzną zmienną (stałą) - tzn. nie jest to tylko funkcja, ale obudowa środowiska, w którym funkcja została utworzona.

W poniższym przykładzie wywołanie zamknięcia g po zmianie x zmieni również wartość x w 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
 6
Author: Mark Cidade,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-19 10:43:31

Oto typowy przypadek użycia closures - wywołań zwrotnych dla elementów GUI (byłaby to alternatywa dla podklasowania klasy button). Na przykład, można skonstruować funkcję, która zostanie wywołana w odpowiedzi na naciśnięcie przycisku i "zamknij" nad odpowiednimi zmiennymi w zakresie nadrzędnym, które są niezbędne do przetworzenia kliknięcia. W ten sposób można podłączyć dość skomplikowane interfejsy z tej samej funkcji inicjalizacji, budując wszystkie zależności do zamknięcia.

 3
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-23 16:13:53

W Pythonie zamknięcie jest instancją funkcji, która ma zmienne związane z nią niezmiennie.

W rzeczywistości model danych wyjaśnia to w swoim opisie atrybutu funkcji " __closure__:

Brak lub krotka komórek , które zawierają powiązania dla zmiennych wolnych funkcji. Tylko do odczytu

Aby zademonstrować to:

def enclosure(foo):
    def closure(bar):
        print(foo, bar)
    return closure

closure_instance = enclosure('foo')

Oczywiście wiemy, że teraz mamy funkcję wskazywaną od nazwy zmiennej closure_instance. Pozornie, jeśli wywołamy go z obiektem, bar, powinien on wydrukować łańcuch znaków, 'foo' i niezależnie od tego, jaka jest reprezentacja łańcuchów bar.

W rzeczywistości łańcuch " foo " jest związany z instancją funkcji i możemy go bezpośrednio odczytać tutaj, uzyskując dostęp do atrybutu cell_contents pierwszej (i jedynej) komórki w krotce atrybutu __closure__:

>>> closure_instance.__closure__[0].cell_contents
'foo'

Na marginesie, obiekty komórek są opisane w dokumentacji C API:

"Cell" obiekty służą do zaimplementuj zmienne odwołujące się przez wiele lunety

I 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 tego nie 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 używa zamknięcia jako funkcji częściowej, 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

Są też bardziej skomplikowane zamknięcia, które nie dopasuj przykład funkcji częściowej, a ja zademonstruję je dalej w miarę czasu.

 1
Author: Aaron Hall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-28 04:00:32

Oto przykład zamknięć Python3

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
 1
Author: thiagoh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-22 20:16:22

Dla mnie "zamknięcia" są funkcjami, które są w stanie zapamiętać środowisko, które zostały stworzone. Ta funkcjonalność, pozwala na użycie zmiennych lub metod wewnątrz zamknięcia, w których, w inny sposób, nie byłoby w stanie użyć albo, ponieważ nie istnieją już lub są one poza zasięgiem ze względu na zakres. Spójrzmy na ten kod w ruby:

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 dlatego, że zdolność zamknięcia do zapamiętania.

 0
Author: Ricardo Avila,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-18 02:56:09
# 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, jakie powinny spełniać zamknięcia To:

  1. musimy mieć zagnieżdżoną funkcję.
  2. zagnieżdżona funkcja musi odnosić się do wartości zdefiniowanej w funkcji zamykającej.
  3. Funkcja zamykająca musi zwracać zagnieżdżoną funkcję.

# 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
 0
Author: Dinesh Sonachalam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-26 16:41:29

Wszyscy używaliśmy dekoratorów w Pythonie. Są to ładne przykłady, aby pokazać, czym są funkcje zamknięcia 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 wartość końcowa to 12

Tutaj funkcja wrapper może uzyskać dostęp do obiektu func, ponieważ wrapper jest "zamknięciem leksykalnym", może uzyskać dostęp do jego nadrzędnych atrybutów. Dlatego jest w stanie uzyskać dostęp do obiektu func.

 0
Author: Nitish Chauhan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-17 19:00:31

Chciałbym podzielić się moim przykładem i wyjaśnieniem na temat zamknięć. Zrobiłem przykład Pythona i dwie liczby, 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 będzie następujący:

*****      hello      #####

      good bye!    ♥♥♥

Oto dwie figury pokazujące stosy i zamknięcie dołączone do obiektu funkcji.

Gdy funkcja jest zwracana z Makera

Gdy funkcja zostanie wywołana później

Gdy funkcja jest wywoływana przez parametr lub zmienna nielokalna, kod wymaga lokalnych powiązań zmiennych, takich jak margin_top, padding, a także a, b, n. w celu zapewnienia działania kodu funkcji, ramka stosu funkcji maker, która odeszła dawno temu, powinna być dostępna, co jest wspierane w zamknięciu, które możemy znaleźć wraz z obiektem funkcji 'message'.

 0
Author: Eunjung Lee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-12 04:23:09

Najlepszym wyjasnieniem jakie kiedykolwiek widzialem zamkniecia bylo wyjasnienie mechanizmu. Poszło coś takiego:

Wyobraź sobie stos programu jako zdegenerowane drzewo, w którym każdy węzeł ma tylko jedno dziecko, a pojedynczy węzeł liścia jest kontekstem aktualnie wykonywanej procedury.

Teraz rozluźnij ograniczenie, że każdy węzeł może mieć tylko jedno dziecko.

Jeśli to zrobisz, możesz mieć konstrukt ('yield'), który może powrócić z procedury bez odrzucania lokalnego kontekstu (tj. nie zdejmuje go ze stosu po powrocie). Przy następnym wywołaniu procedury, wywołanie pobiera starą ramkę stosu (drzewa) i kontynuuje wykonywanie tam, gdzie zostało przerwane.

 -2
Author: ConcernedOfTunbridgeWells,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-18 17:10:42