Frage

Ich habe viel über Schließungen gelesen und denke, dass ich sie verstehe, aber ohne das Bild für mich und andere zu trüben, hoffe ich, dass jemand Schließungen so prägnant 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.

War es hilfreich?

Lösung

Verschluss über Verschlüsse

Objekte sind Daten mit angeschlossenen Methoden, Verschlü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

Andere Tipps

Es ist einfach:Eine Funktion, die auf Variablen aus einem enthaltenden Bereich verweist, möglicherweise nachdem der Kontrollfluss diesen Bereich verlassen hat.Das letzte Bit 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 innerhalb von f bzw. g „verschwunden“ sind. Dieses Merkmal macht f und g zu richtigen Abschlüssen.

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 in eine Funktion binden ohne sie als Parameter zu übergeben.

Dekoratoren, die Parameter akzeptieren, werden häufig für Abschlüsse verwendet.Abschlüsse sind ein gängiger Implementierungsmechanismus für diese Art von „Funktionsfabrik“.Ich entscheide mich häufig für die Verwendung von Verschlüssen in der Strategiemuster wenn die Strategie durch Daten zur Laufzeit geändert wird.

In einer Sprache, die anonyme Blockdefinitionen ermöglicht – z. B. Ruby, C# – können Abschlüsse verwendet werden, um (was auch immer) neuartige neue Kontrollstrukturen zu implementieren.Das Fehlen anonymer Blöcke gehört dazu die Einschränkungen von Abschlüssen in Python.

Um ehrlich zu sein, verstehe ich Schließungen vollkommen gut, außer dass mir nie klar geworden ist, was genau „Abschluss“ ist und was daran so „Abschluss“ ist.Ich empfehle Ihnen, die Suche nach einer Logik hinter der Begriffswahl 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 zur lokalen Variable „x“ beibehält, auch wenn „x“ den Gültigkeitsbereich verlassen hat und nicht mehr funktionieren sollte.Dieser Hook bezieht sich auf die Variable selbst und nicht nur auf den Wert, den die Variable zu diesem Zeitpunkt hatte. Wenn also bar aufgerufen wird, wird 5 und nicht 3 ausgegeben.

Seien Sie sich auch darüber im Klaren, dass Python 2.x über eine begrenzte Schließung verfügt:Es gibt keine Möglichkeit, „x“ innerhalb von „bar“ zu ändern, da das Schreiben von „x = bla“ ein lokales „x“ in bar deklarieren und nicht „x“ von foo zuweisen würde.Dies ist ein Nebeneffekt der Zuweisung=Deklaration von Python.Um dies zu umgehen, führt Python 3.0 das Schlüsselwort nonlocal 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

Ich habe noch nie davon gehört, dass Transaktionen im gleichen Kontext verwendet werden wie die Erklärung, was ein Abschluss ist, und es gibt hier wirklich keine Transaktionssemantik.

Es wird als Abschluss bezeichnet, weil es die äußere Variable (Konstante) „schließt“ – d. h. es handelt sich nicht nur um eine Funktion, sondern um eine Umschließung der Umgebung, in der die Funktion erstellt wurde.

Im folgenden Beispiel ändert der Aufruf des Abschlusses g nach der Änderung 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

Hier ist ein typischer Anwendungsfall für Abschlüsse – Rückrufe für GUI-Elemente (dies wäre eine Alternative zur Unterklasse der Schaltflächenklasse).Sie können beispielsweise eine Funktion konstruieren, 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 mit derselben Initialisierungsfunktion verbinden und alle Abhängigkeiten in den Abschluss einbauen.

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

Tatsächlich ist die Das Datenmodell erklärt dies in seiner Funktionsbeschreibung __closure__ Attribut:

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

Offensichtlich wissen wir, dass wir jetzt eine Funktion haben, auf die der Variablenname zeigt closure_instance.Angeblich, wenn wir es mit einem Objekt aufrufen, bar, es sollte die Zeichenfolge drucken, 'foo' und was auch immer die String-Darstellung davon ist bar Ist.

Tatsächlich ist die Zeichenfolge „foo“ Ist an die Instanz der Funktion gebunden, und wir können sie hier direkt lesen, indem wir auf die zugreifen cell_contents Attribut der ersten (und einzigen) Zelle im Tupel der __closure__ Attribut:

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

Abgesehen davon werden Zellobjekte in der C-API-Dokumentation beschrieben:

Objekte "Zellen" werden verwendet, um Variablen zu implementieren, auf die sich mehrere Scopes beziehen

Und wir können die Verwendung unseres Verschlusses demonstrieren, indem wir dies beachten 'foo' bleibt in der Funktion hängen und ändert sich nicht:

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

Das angegebene Beispiel verwendet die Schließung als Teilfunktion, aber wenn dies unser einziges Ziel ist, kann das gleiche Ziel auch damit 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 Abschlüsse, die nicht zum Teilfunktionsbeispiel passen würden, und ich werde sie weiter demonstrieren, wenn die Zeit es erlaubt.

Hier ist ein Beispiel für Python3-Abschlüsse

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

Kriterien, die Schließungen erfüllen müssen, sind:

  1. Wir müssen eine verschachtelte Funktion haben.
  2. Eine verschachtelte Funktion muss sich auf den in der einschließ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

Für mich sind „Abschlüsse“ Funktionen, die sich an die Umgebung erinnern können, in der sie erstellt wurden.Mit dieser Funktionalität können Sie innerhalb des Abschlusses Variablen oder Methoden verwenden, die Sie andernfalls nicht verwenden könnten, weil sie nicht mehr existieren oder aufgrund ihres Gültigkeitsbereichs außer Reichweite 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 Methode „multiplizieren“ als auch die Variable „x“ nicht mehr existieren.Alles nur wegen der Fähigkeit des Abschlusses, sich zu erinnern.

wir alle haben es benutzt Dekorateure in Python.Sie sind schöne Beispiele, um zu zeigen, was Abschlussfunktionen 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 Funktionsobjekt zugreifen, da der Wrapper ein „lexikalischer Abschluss“ ist und auf seine übergeordneten Attribute zugreifen kann.Aus diesem Grund kann auf das Funktionsobjekt zugegriffen werden.

Ich möchte mein Beispiel und eine Erklärung zu Schließungen teilen.Ich habe ein Python-Beispiel und zwei Abbildungen erstellt, 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 an das Funktionsobjekt angehängten Abschluss zeigen.

wenn die Funktion vom Hersteller zurückgegeben wird

wenn die Funktion später aufgerufen wird

Wenn die Funktion über einen Parameter oder eine nicht-lokale 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 Stack-Frame der Maker-Funktion zugegriffen werden können, der schon vor langer Zeit verschwunden ist. Dieser wird im Abschluss gesichert, den wir zusammen mit dem Funktionsobjekt „message“ finden.

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

Stellen Sie sich Ihren Programmstapel als einen degenerierten 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 („yield“) haben, das von einer Prozedur zurückkehren kann, ohne den lokalen Kontext zu verwerfen (d. h.es fällt nicht vom Stapel, wenn Sie zurückkommen).Wenn die Prozedur das nächste Mal aufgerufen wird, greift der Aufruf den alten Stapelrahmen (Baumrahmen) auf und setzt die Ausführung dort fort, wo er aufgehört hat.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top