Können Sie Schließungen erklären (da sie sich auf Python beziehen)?


82

Ich habe viel über Verschlüsse gelesen und glaube, ich verstehe sie, aber ohne das Bild für mich und andere zu trüben, hoffe ich, dass jemand Verschlüsse so kurz und klar wie möglich erklären kann. Ich suche nach einer einfachen Erklärung, die mir helfen könnte zu verstehen, wo und warum ich sie verwenden möchte.

Antworten:


93

Verschluss bei Verschlüssen

Objekte sind Daten mit angehängten Methoden, Abschlüsse sind Funktionen mit angehängten Daten.

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
Beachten Sie, dass nonlocalPython 2.x in Python 3 hinzugefügt wurde und keine vollständigen Lese- / Schreibabschlüsse hatte (dh Sie konnten geschlossene Variablen lesen, aber ihre Werte nicht ändern)
James Porter

6
@JamesPorter: Hinweis: Sie können das nonlocalSchlüsselwort in Python 2 mithilfe eines veränderlichen Objekts emulieren, z. B. können L = [0] \n def counter(): L[0] += 1; return L[0]Sie den Namen in diesem Fall nicht ändern (an ein anderes Objekt binden), aber Sie können das veränderbare Objekt selbst ändern, auf das sich der Name bezieht zu. Die Liste ist erforderlich, da Ganzzahlen in Python unveränderlich sind.
JFS

1
@ JFSebastian: richtig. das fühlt sich immer wie ein schmutziger, schmutziger Hack an :)
James Porter

45

Es ist ganz einfach: Eine Funktion, die auf Variablen aus einem enthaltenen Bereich verweist, möglicherweise nachdem der Kontrollfluss diesen Bereich verlassen hat. Das letzte bisschen ist sehr nützlich:

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

Beachten Sie, dass 12 und 4 in f bzw. g "verschwunden" sind. Diese Funktion macht f und g zu richtigen Verschlüssen.


Es besteht keine Notwendigkeit zu tun constant = x; Sie könnten dies einfach return y + xin der verschachtelten Funktion tun (oder das Argument mit dem Namen erhalten constant), und es würde gut funktionieren. Argumente werden von der Schließung nicht anders erfasst als nicht argumentative Einheimische.
ShadowRanger

14

Ich mag diese grobe, prägnante Definition :

Eine Funktion, die sich auf Umgebungen beziehen kann, die nicht mehr aktiv sind.

Ich würde hinzufügen

Mit einem Abschluss können Sie Variablen an eine Funktion binden, ohne sie als Parameter zu übergeben .

Dekorateure, die Parameter akzeptieren, werden häufig für Verschlüsse verwendet. Closures sind ein gängiger Implementierungsmechanismus für diese Art von "Function Factory". Ich verwende häufig Abschlüsse im Strategiemuster, wenn die Strategie zur Laufzeit durch Daten geändert wird.

In einer Sprache, die eine anonyme Blockdefinition ermöglicht - z. B. Ruby, C # - können Verschlüsse verwendet werden, um neuartige neue Kontrollstrukturen zu implementieren (wie viel). Das Fehlen anonymer Blöcke gehört zu den Einschränkungen von Schließungen in Python .


14

Um ehrlich zu sein, verstehe ich Verschlüsse sehr gut, außer dass mir nie klar war, was genau das ist, was der "Verschluss" ist und was so "Verschluss" daran ist. Ich empfehle Ihnen, die Suche nach einer Logik für die Wahl des Begriffs aufzugeben.

Wie auch immer, hier ist meine Erklärung:

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

bar = foo()
bar()   # print 5

Eine Schlüsselidee hierbei ist, dass das von foo zurückgegebene Funktionsobjekt einen Hook für die lokale Variable 'x' behält, obwohl 'x' den Gültigkeitsbereich verlassen hat und nicht mehr gültig sein sollte. Dieser Hook bezieht sich auf die Variable selbst, nicht nur auf den Wert, den var zu diesem Zeitpunkt hatte. Wenn also bar aufgerufen wird, wird 5 und nicht 3 ausgegeben.

Stellen Sie außerdem klar, dass Python 2.x nur einen begrenzten Abschluss hat: Ich kann 'x' in 'bar' auf keinen Fall ändern, da durch das Schreiben von 'x = bla' ein lokales 'x' in bar deklariert und nicht 'x' von foo zugewiesen wird . Dies ist ein Nebeneffekt von Pythons Zuweisung = Deklaration. Um dies zu umgehen, führt Python 3.0 das nichtlokale Schlüsselwort ein:

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

6

Ich habe noch nie von Transaktionen gehört, die im selben Kontext wie die Erklärung eines Abschlusses verwendet werden, und hier gibt es wirklich keine Transaktionssemantik.

Es wird als Abschluss bezeichnet, weil es die äußere Variable (Konstante) "schließt" - dh es ist nicht nur eine Funktion, sondern ein Gehäuse der Umgebung, in der die Funktion erstellt wurde.

Im folgenden Beispiel ändert das Aufrufen des Abschlusses g nach dem Ändern von x auch den Wert von x innerhalb von g, da g über x schließt:

x = 0

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


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

So wie es aussieht, g()berechnet es x * 2, gibt aber nichts zurück. Das sollte sein return x * 2. +1 dennoch für eine Erklärung für das Wort "Schließung".
Bruno Le Floch

3

Hier ist ein typischer Anwendungsfall für Schließungen - Rückrufe für GUI-Elemente (dies wäre eine Alternative zur Unterklasse der Schaltflächenklasse). Sie können beispielsweise eine Funktion erstellen, die als Reaktion auf einen Tastendruck aufgerufen wird, und die relevanten Variablen im übergeordneten Bereich, die für die Verarbeitung des Klicks erforderlich sind, "schließen". Auf diese Weise können Sie ziemlich komplizierte Schnittstellen über dieselbe Initialisierungsfunktion verkabeln und alle Abhängigkeiten in den Abschluss einbauen.


1

In Python ist ein Abschluss eine Instanz einer Funktion, an die Variablen unveränderlich gebunden sind.

Tatsächlich erklärt das Datenmodell dies in seiner Beschreibung des Funktionsattributs __closure__:

Keine oder ein Tupel von Zellen , die Bindungen für die freien Variablen der Funktion enthalten. Schreibgeschützt

Um dies zu demonstrieren:

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

closure_instance = enclosure('foo')

Es ist klar, dass wir jetzt eine Funktion haben, auf die der Variablenname zeigt closure_instance. Wenn wir es mit einem Objekt aufrufen, sollte es angeblich bardie Zeichenfolge drucken 'foo'und unabhängig von der Zeichenfolgendarstellung bar.

In der Tat, die Zeichenfolge ‚foo‘ wird auf die Instanz der Funktion gebunden, und wir können es direkt hier lesen, indem Sie den Zugriff auf cell_contentsAttribut des ersten (und einzigen) Zelle in dem Tupel des __closure__Attributs:

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

Nebenbei werden Zellobjekte in der C-API-Dokumentation beschrieben:

"Zellen" -Objekte werden verwendet, um Variablen zu implementieren, auf die von mehreren Bereichen verwiesen wird

Und wir können die Verwendung unseres Verschlusses demonstrieren, indem wir feststellen, dass 'foo'er in der Funktion steckt und sich nicht ändert:

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

Und nichts kann es ändern:

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

Teilfunktionen

In dem angegebenen Beispiel wird der Verschluss als Teilfunktion verwendet. Wenn dies jedoch unser einziges Ziel ist, kann dasselbe Ziel erreicht werden 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

Es gibt auch kompliziertere Verschlüsse, die nicht zum Teilfunktionsbeispiel passen, und ich werde sie weiter demonstrieren, wenn es die Zeit erlaubt.


1

Hier ist ein Beispiel für Python3-Schließungen

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

Von Closures zu erfüllende Kriterien sind:

  1. Wir müssen eine verschachtelte Funktion haben.
  2. Die verschachtelte Funktion muss sich auf den in der umschließenden Funktion definierten Wert beziehen.
  3. Die einschließende Funktion muss die verschachtelte Funktion zurückgeben.

# 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

Für mich sind "Verschlüsse" Funktionen, die sich an die Umgebung erinnern können, in der sie erstellt wurden. Mit dieser Funktion können Sie Variablen oder Methoden innerhalb des Abschlusses verwenden, die Sie auf andere Weise nicht verwenden können, weil sie nicht mehr vorhanden sind oder aufgrund des Umfangs nicht erreichbar sind. Schauen wir uns diesen Code in Ruby an:

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"  

Es funktioniert auch dann, wenn sowohl die "Multiplikations" -Methode als auch die "x" -Variable nicht mehr vorhanden sind. Alles nur, weil die Schließfähigkeit in Erinnerung bleibt.


0

Wir alle haben Decorators in Python verwendet. Sie sind schöne Beispiele, um zu zeigen, was Schließfunktionen in Python sind.

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)

hier ist der Endwert 12

Hier kann die Wrapper-Funktion auf das func-Objekt zugreifen, da der Wrapper "lexikalischer Abschluss" ist und auf die übergeordneten Attribute zugreifen kann. Aus diesem Grund kann auf func-Objekte zugegriffen werden.


0

Ich möchte mein Beispiel und eine Erklärung zu Schließungen mitteilen. Ich habe ein Python-Beispiel und zwei Abbildungen gemacht, um die Stapelzustände zu demonstrieren.

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

Die Ausgabe dieses Codes wäre wie folgt:

*****      hello      #####

      good bye!    ♥♥♥

Hier sind zwei Abbildungen, die Stapel und den am Funktionsobjekt angebrachten Verschluss zeigen.

wenn die Funktion vom Hersteller zurückgegeben wird

wenn die Funktion später aufgerufen wird

Wenn die Funktion über einen Parameter oder eine nichtlokale Variable aufgerufen wird, benötigt der Code lokale Variablenbindungen wie margin_top, padding sowie a, b, n. Um sicherzustellen, dass der Funktionscode funktioniert, sollte auf den Stapelrahmen der Maker-Funktion zugegriffen werden können, der vor langer Zeit entfernt wurde. Dies wird in dem Abschluss gesichert, den wir zusammen mit dem Funktionsobjekt der Nachricht finden.


-2

Die beste Erklärung, die ich je für einen Verschluss gesehen habe, war die Erklärung des Mechanismus. Es ging ungefähr so:

Stellen Sie sich Ihren Programmstapel als einen entarteten Baum vor, in dem jeder Knoten nur ein untergeordnetes Element hat und der einzelne Blattknoten den Kontext Ihrer aktuell ausgeführten Prozedur darstellt.

Lockern Sie nun die Einschränkung, dass jeder Knoten nur ein Kind haben kann.

Wenn Sie dies tun, können Sie ein Konstrukt ('Ausbeute') haben, das von einer Prozedur zurückkehren kann, ohne den lokalen Kontext zu verwerfen (dh es wird bei Ihrer Rückkehr nicht vom Stapel genommen). Beim nächsten Aufruf der Prozedur nimmt der Aufruf den alten Stapelrahmen (Baumrahmen) auf und setzt die Ausführung dort fort, wo er aufgehört hat.


Das ist KEINE Erklärung für Schließungen.
Jules

Sie beschreiben Fortsetzungen, keine Abschlüsse.
Matthew Olenik
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.