Kapanışları açıklayabilir misiniz (Python ile ilgili oldukları gibi)?


85

Kapanışlar hakkında çok şey okudum ve sanırım onları anlıyorum, ancak resmi kendim ve başkaları için bulanıklaştırmadan, birinin kapanışları olabildiğince kısa ve net bir şekilde açıklayabileceğini umuyorum. Bunları nerede ve neden kullanmak istediğimi anlamama yardımcı olabilecek basit bir açıklama arıyorum.

Yanıtlar:


97

Kapanışlarda kapatma

Nesneler, yöntemlerin eklendiği verilerdir, kapanışlar ise verilerin eklendiği işlevlerdir.

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

6
Not nonlocalpiton 3'te eklenmiş, piton 2.x yoktu tam üzerinde, okuma-yazma kapamaları (yani değişkenler üzerinde kapalı, ama onların değerlerini değiştirmez okumak olabilir)
James Porter

6
@JamesPorter: not: nonlocalPython 2'de değiştirilebilir bir nesne kullanarak anahtar kelimeyi taklit edebilirsiniz , L = [0] \n def counter(): L[0] += 1; return L[0]örneğin, bu durumda adı değiştiremezsiniz (başka bir nesneye bağlayamazsınız) ancak adın ifade ettiği değiştirilebilir nesnenin kendisini değiştirebilirsiniz için. Python'da tam sayılar değişmez olduğu için liste gereklidir.
jfs

1
@JFSebastian: doğru. bu her zaman kirli, kirli bir hack gibi geliyor :)
James Porter

46

Çok basit: Muhtemelen kontrol akışı bu kapsamdan ayrıldıktan sonra, kapsayıcı bir kapsamdaki değişkenlere başvuran bir işlev. Bu son kısım çok yararlı:

>>> 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

12 ve 4'ün sırasıyla f ve g içinde "kaybolduğunu" unutmayın, bu özellik f ve g'yi uygun kapanışlar yapan şeydir.


Yapmaya gerek yok constant = x; sadece return y + xiç içe geçmiş fonksiyonda yapabilir (veya isimle argümanı alabilir constant) ve gayet iyi çalışır; bağımsız değişkenler, bağımsız değişken olmayan yerellerden farklı olmayan bir şekilde kapanış tarafından yakalanır.
ShadowRanger

15

Bu kaba, kısa tanımı beğendim :

Artık aktif olmayan ortamlara atıfta bulunabilen bir işlev.

Eklerdim

Kapanış, değişkenleri parametre olarak iletmeden bir işleve bağlamanıza izin verir .

Parametreleri kabul eden dekoratörler, kapanışlar için yaygın bir kullanımdır. Kapanışlar, bu tür "işlev fabrikası" için ortak bir uygulama mekanizmasıdır. Strateji Modelinde sık sık kapanışları kullanmayı seçiyorum , çalışma zamanında veriler tarafından değiştirildiğinde .

Anonim blok tanımına izin veren bir dilde - örneğin Ruby, C # - kapatmalar yeni yeni kontrol yapıları (ne kadar) uygulamak için kullanılabilir. Anonim blokların olmaması, Python'daki kapatma sınırlamaları arasındadır .


15

Dürüst olmak gerekirse, kapanışları çok iyi anlıyorum, ancak "kapanış" olan şeyin tam olarak ne olduğu ve bu konuda bu kadar "kapanış" olan şeyin tam olarak ne olduğu konusunda hiçbir zaman net olamadım. Terim seçiminin ardındaki mantığı aramaktan vazgeçmenizi tavsiye ederim.

Her neyse, işte açıklamam:

def foo():
   x = 3
   def bar():
      print x
   x = 5
   return bar

bar = foo()
bar()   # print 5

Buradaki temel fikir, foo'dan döndürülen işlev nesnesinin, 'x' kapsam dışına çıkmasına ve geçersiz kılınması gerekmesine rağmen yerel var 'x'e bir kanca tutmasıdır. Bu kanca var'ın kendisi içindir, sadece var'ın o sırada sahip olduğu değer değil, bu nedenle bar çağrıldığında 3 değil 5 yazdırır.

Ayrıca Python 2.x'in sınırlı kapanma oranına sahip olduğu açık: 'bar'ın içindeki' x'i değiştiremem, çünkü 'x = bla' yazmak foo'nun 'x' yerine yerel bir 'x' bildirir. . Bu, Python'un atama = beyanının bir yan etkisidir. Python 3.0, bunu aşmak için yerel olmayan anahtar kelimeyi sunar:

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

7

İşlemlerin kapanışın ne olduğunu açıklamakla aynı bağlamda kullanıldığını hiç duymadım ve burada gerçekten herhangi bir işlem semantiği yok.

Kapatma olarak adlandırılır çünkü dış değişkeni (sabit) "kapatır" - yani, bu sadece bir işlev değil, işlevin oluşturulduğu ortamın bir kapsamıdır.

Aşağıdaki örnekte, x'i değiştirdikten sonra g kapanışını çağırmak, g içindeki x değerini de değiştirecektir, çünkü g x'in üzerine kapanır:

x = 0

def f():
    def g(): 
        return x * 2
    return g


closure = f()
print(closure()) # 0
x = 2
print(closure()) # 4

Ayrıca, olduğu gibi g()hesaplar x * 2ama hiçbir şey döndürmez. Bu olmalı return x * 2. +1 yine de "kapanış" kelimesinin açıklaması için.
Bruno Le Floch

3

İşte kapamalar için tipik bir kullanım durumu - GUI öğeleri için geri çağırmalar (bu, düğme sınıfını alt sınıflara ayırmaya bir alternatif olabilir). Örneğin, bir düğmeye basıldığında çağrılacak bir işlev oluşturabilir ve tıklamayı işlemek için gerekli olan üst kapsamdaki ilgili değişkenler üzerinde "kapatabilirsiniz". Bu şekilde, oldukça karmaşık arayüzleri aynı başlatma işlevinden bağlayarak tüm bağımlılıkları kapanışa kurabilirsiniz.


2

Python'da bir kapanış, kendisine sabit bir şekilde bağlı değişkenlere sahip bir işlev örneğidir.

Aslında, veri modeli bunu işlevlerin __closure__özniteliğinin açıklamasında açıklar :

Hiçbiri veya işlevin serbest değişkenleri için bağlar içeren bir hücre kümesi . Sadece oku

Bunu göstermek için:

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

closure_instance = enclosure('foo')

Açıkça, artık değişken adından işaret ettiğimiz bir işleve sahip olduğumuzu biliyoruz closure_instance. Görünüşe göre, eğer onu bir nesneyle çağırırsak bar, dizeyi 'foo've dizgi temsili ne olursa olsun yazdırmalıdır .bar .

Aslında, dize 'foo' olan fonksiyonun örneğine bağlı ve doğrudan erişerek, buradan okuyabilirsiniz cell_contentsait başlığın ilk (ve tek) hücrenin niteliğini __closure__özniteliği:

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

Bir kenara, hücre nesneleri C API belgelerinde açıklanmıştır:

"Hücre" nesneleri, birden çok kapsam tarafından referans verilen değişkenleri uygulamak için kullanılır

Kapanışımızın kullanımını gösterebiliriz 'foo', bunun işlevde takılı kaldığını ve değişmediğini belirterek :

>>> closure_instance('bar')
foo bar
>>> closure_instance('baz')
foo baz
>>> closure_instance('quux')
foo quux

Ve onu hiçbir şey değiştiremez:

>>> closure_instance.__closure__ = None
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: readonly attribute

Kısmi İşlevler

Verilen örnek, kapanışı kısmi bir işlev olarak kullanır, ancak tek amacımız buysa, aynı hedefe şu şekilde de ulaşılabilir: 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

Kısmi fonksiyon örneğine uymayan daha karmaşık kapanışlar da var ve bunları zaman elverdikçe daha fazla göstereceğim.


2
# 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

Kapanışlar tarafından karşılanacak kriterler şunlardır:

  1. İç içe geçmiş bir işleve sahip olmalıyız.
  2. İç içe geçmiş işlev, çevreleyen işlevde tanımlanan değere başvurmalıdır.
  3. Çevreleyen işlev, yuvalanmış işlevi döndürmelidir.

# 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

1

İşte Python3 kapanışlarının bir örneği

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

0

Benim için "kapatmalar", yaratıldıkları ortamı hatırlayabilen işlevlerdir. Bu işlevsellik, kapanış içinde değişkenleri veya yöntemleri kullanmanıza izin verir, başka bir şekilde, artık var olmadıkları için veya kapsam nedeniyle erişilemedikleri için kullanamazsınız. Ruby'deki bu koda bakalım:

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"  

hem "çarpma" yöntemi hem de "x" değişkeni artık mevcut olmadığında bile çalışır. Hepsi çünkü hatırlama yeteneği kapanma.


0

hepimiz python'da Dekoratörleri kullandık . Python'da kapatma fonksiyonlarının ne olduğunu göstermek için güzel örneklerdir.

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)

burada son değer 12

Burada, sarmalayıcı işlevi, işlev nesnesine erişebilir çünkü sarmalayıcı "sözcüksel kapanış" tır, üst özniteliklerine erişebilir. Bu nedenle, func nesnesine erişebilir.


0

Örneğimi ve kapanışlarla ilgili bir açıklamamı paylaşmak istiyorum. Bir python örneği ve yığın durumlarını göstermek için iki şekil yaptım.

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!')

Bu kodun çıktısı aşağıdaki gibi olacaktır:

*****      hello      #####

      good bye!    ♥♥♥

Yığınları ve işlev nesnesine iliştirilmiş kapağı gösteren iki şekil.

işlev maker'dan iade edildiğinde

işlev daha sonra çağrıldığında

İşlev bir parametre veya yerel olmayan bir değişken aracılığıyla çağrıldığında, kod, margin_top, padding ve a, b, n gibi yerel değişken bağlamalarına ihtiyaç duyar. İşlev kodunun çalışmasını sağlamak için, uzun zaman önce ortadan kaldırılan maker işlevinin yığın çerçevesine erişilebilir olması gerekir, bu da 'mesajın işlev nesnesi ile birlikte bulabileceğimiz kapanışta yedeklenir.


-2

Bir kapanmanın şimdiye kadar gördüğüm en iyi açıklaması, mekanizmayı açıklamaktı. Bunun gibi bir şey gitti:

Program yığınınızı, her düğümün yalnızca bir çocuğu olduğu ve tek yaprak düğümün şu anda yürütmekte olduğunuz prosedürün bağlamı olduğu dejenere bir ağaç olarak düşünün.

Şimdi her düğümün yalnızca bir çocuğu olabileceği kısıtını gevşetin.

Bunu yaparsanız, yerel bağlamı atmadan bir prosedürden dönebilen bir yapıya ('verim') sahip olabilirsiniz (yani geri döndüğünüzde onu yığından çıkarmaz). Prosedürün bir sonraki başlatılışında, çağrı eski yığın (ağaç) çerçevesini alır ve kaldığı yerden çalışmaya devam eder.


Bu, kapanışların bir açıklaması DEĞİL.
Jules

Devam etmeleri anlatıyorsun, kapanışları değil
Matthew Olenik
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.