Ich habe verschachtelte Funktionen in Python gesehen und verwendet, die der Definition eines Abschlusses entsprechen. Warum heißen sie nested functions
statt closures
?
Sind verschachtelte Funktionen keine Abschlüsse, weil sie nicht von der Außenwelt verwendet werden?
UPDATE: Ich habe über Schließungen gelesen und über dieses Konzept in Bezug auf Python nachgedacht. Ich habe den Artikel gesucht und gefunden, der von jemandem in einem Kommentar unten erwähnt wurde, aber ich konnte die Erklärung in diesem Artikel nicht vollständig verstehen, deshalb stelle ich diese Frage.
python
closures
nested-function
Srikar Appalaraju
quelle
quelle
Antworten:
Ein Abschluss tritt auf, wenn eine Funktion von einem umschließenden Bereich, der ihre Ausführung beendet hat, Zugriff auf eine lokale Variable hat.
Beim
make_printer
Aufruf wird ein neuer Frame mit dem kompilierten Code für dieprinter
Funktion als Konstante und dem Wertmsg
als lokal auf den Stapel gelegt . Anschließend wird die Funktion erstellt und zurückgegeben. Da die Funktionprinter
auf diemsg
Variable verweist , bleibt sie nach dermake_printer
Rückkehr der Funktion am Leben .Also, wenn Ihre verschachtelten Funktionen dies nicht tun
dann sind sie keine Verschlüsse.
Hier ist ein Beispiel für eine verschachtelte Funktion, die kein Abschluss ist.
Hier binden wir den Wert an den Standardwert eines Parameters. Dies tritt auf, wenn die Funktion
printer
erstellt wird und daher nach der Rückgabe kein Verweis auf den Wert vonmsg
external toprinter
beibehalten werden mussmake_printer
.msg
istprinter
in diesem Zusammenhang nur eine normale lokale Variable der Funktion .quelle
self
. (In JavaScript / Python ist das fast wahr.)i
] aus einem umschließenden Bereich". bezieht sich, dh kann deni
Wert überprüfen (oder ändern) , selbst wenn / wenn dieser Bereich "seine Ausführung beendet hat", dh die Ausführung eines Programms auf andere Teile des Codes übergegangen ist. Der Block, in demi
definiert ist, ist nicht mehr vorhanden, Funktionen, auf diei
noch Bezug genommen wird, können dies jedoch tun. Dies wird allgemein als "Schließen über der Variableni
" beschrieben. Um die spezifischen Variablen nicht zu behandeln, kann sie als Abschluss über den gesamten Umgebungsrahmen implementiert werden, in dem diese Variable definiert ist.Die Frage wurde bereits von aaronasterling beantwortet
Es könnte jedoch jemand interessiert sein, wie die Variablen unter der Haube gespeichert werden.
Bevor Sie zum Snippet kommen:
Closures sind Funktionen, die Variablen von ihrer umgebenden Umgebung erben. Wenn Sie einen Funktionsrückruf als Argument an eine andere Funktion übergeben, die E / A ausführt, wird diese Rückruffunktion später aufgerufen, und diese Funktion merkt sich - fast auf magische Weise - den Kontext, in dem sie deklariert wurde, sowie alle verfügbaren Variablen in diesem Zusammenhang.
Wenn eine Funktion keine freien Variablen verwendet, bildet sie keinen Abschluss.
Wenn es eine andere innere Ebene gibt, die freie Variablen verwendet , speichern alle vorherigen Ebenen die lexikalische Umgebung (Beispiel am Ende).
Funktionsattribute
func_closure
in Python <3.X oder__closure__
in Python> 3.X speichern die freien Variablen.Jede Funktion in Python verfügt über diese Abschlussattribute, speichert jedoch keinen Inhalt, wenn keine freien Variablen vorhanden sind.
Beispiel: von Abschlussattributen, aber ohne Inhalt, da keine freie Variable vorhanden ist.
NB: KOSTENLOSE VARIABLE MÜSSEN EINEN SCHLUSS SCHAFFEN.
Ich werde das gleiche Snippet wie oben erklären:
Und alle Python-Funktionen haben ein Closure-Attribut. Untersuchen wir also die einschließenden Variablen, die einer Closure-Funktion zugeordnet sind.
Hier ist das Attribut
func_closure
für die Funktionprinter
Das
closure
Attribut gibt ein Tupel von Zellenobjekten zurück, die Details der im umschließenden Bereich definierten Variablen enthalten.Das erste Element in func_closure, das None oder ein Tupel von Zellen sein kann, die Bindungen für die freien Variablen der Funktion enthalten, ist schreibgeschützt.
Hier in der obigen Ausgabe sehen Sie
cell_contents
, was darin gespeichert ist:Wenn wir die Funktion
printer()
aufrufen, greift sie auf den Wert zu, der in der Funktion gespeichert istcell_contents
. So haben wir die Ausgabe als 'Foo!'Wieder werde ich die Verwendung des obigen Snippets mit einigen Änderungen erklären:
Im obigen Snippet drucke ich keine Nachricht innerhalb der Druckerfunktion, sodass keine freie Variable erstellt wird. Da es keine freie Variable gibt, enthält der Abschluss keinen Inhalt. Genau das sehen wir oben.
Jetzt werde ich ein anderes Snippet erklären, um alles zu klären
Free Variable
mitClosure
:Wir sehen also, dass eine
func_closure
Eigenschaft ein Tupel von Abschlusszellen ist . Wir können sie und ihren Inhalt explizit referenzieren - eine Zelle hat die Eigenschaft "cell_contents".Wenn wir hier aufrufen
inn
, werden alle speicherfreien Variablen referenziert, damit wir sie erhaltenI am free variable
quelle
func_closure
wird jetzt__closure__
ähnlich wie bei den verschiedenen anderenfunc_*
Attributen aufgerufen .__closure_
ist in Python 2.6+ für die Kompatibilität mit Python 3.__closure__
Objekt, das geschlossen wird.Python hat eine schwache Unterstützung für das Schließen. Um zu sehen, was ich meine, nehmen Sie das folgende Beispiel eines Zählers, der mit JavaScript geschlossen wird:
Das Schließen ist ziemlich elegant, da es Funktionen, die so geschrieben sind, die Möglichkeit gibt, "internen Speicher" zu haben. Ab Python 2.7 ist dies nicht möglich. Wenn du es versuchst
Sie erhalten eine Fehlermeldung, dass x nicht definiert ist. Aber wie kann das sein, wenn andere gezeigt haben, dass Sie es drucken können? Dies liegt daran, wie Python den Funktionsvariablenbereich verwaltet. Während die innere Funktion die Variablen der äußeren Funktion lesen kann, kann sie sie nicht schreiben .
Das ist wirklich eine Schande. Mit nur schreibgeschütztem Abschluss können Sie jedoch zumindest das Funktionsdekorationsmuster implementieren , für das Python syntaktischen Zucker anbietet.
Aktualisieren
Wie bereits erwähnt, gibt es Möglichkeiten, mit den Einschränkungen des Python-Bereichs umzugehen, und ich werde einige davon offenlegen.
1. Verwenden Sie das
global
Schlüsselwort (im Allgemeinen nicht empfohlen).2. Verwenden Sie in Python 3.x das
nonlocal
Schlüsselwort (vorgeschlagen von @unutbu und @leewz).3. Definieren Sie eine einfache modifizierbare Klasse
Object
und erstellen Sie ein
Object scope
InneresinitCounter
, um die Variablen zu speichernDa
scope
es sich eigentlich nur um eine Referenz handelt, ändernscope
sich die mit den Feldern ausgeführten Aktionen nicht wirklich selbst, sodass kein Fehler auftritt.4. Eine alternative Möglichkeit wäre, wie @unutbu hervorhob, jede Variable als Array (
x = [0]
) zu definieren und ihr erstes Element (x[0] += 1
) zu ändern . Auch hier tritt kein Fehler auf, dax
selbst nicht geändert wird.5. Wie von @raxacoricofallapatorius vorgeschlagen, können Sie
x
eine Eigenschaft von erstellencounter
quelle
x = [0]
im äußeren Bereich erstellen undx[0] += 1
im inneren Bereich verwenden. In Python3 können Sie Ihren Code unverändert lassen und das nichtlokale Schlüsselwort verwenden .x
zeigt, bleibt genau gleich, selbst wenn Sie aufrufeninc()
oder was auch immer, und Sie haben nicht effektiv in die Variable geschrieben.x
eine Eigenschaftcounter
.nonlocal
Schlüsselwort, dasglobal
nur für die Variablen einer äußeren Funktion gilt. Dadurch kann eine innere Funktion einen Namen von ihren äußeren Funktionen neu binden. Ich denke, "an den Namen binden" ist genauer als "die Variable ändern".Python 2 hatte keine Abschlüsse - es gab Problemumgehungen, die Abschlüssen ähnelten .
Es gibt viele Beispiele für bereits gegebene Antworten - Kopieren von Variablen in die innere Funktion, Ändern eines Objekts in der inneren Funktion usw.
In Python 3 ist die Unterstützung expliziter - und prägnant:
Verwendung:
Das
nonlocal
Schlüsselwort bindet die innere Funktion an die explizit erwähnte äußere Variable und schließt sie tatsächlich ein. Daher expliziter eine "Schließung".quelle
Ich hatte eine Situation, in der ich einen separaten, aber dauerhaften Namensraum brauchte. Ich habe Klassen benutzt. Ich nicht anders. Getrennte, aber dauerhafte Namen sind Schließungen.
quelle
Gibt:
Dies ist ein Beispiel dafür, was ein Verschluss ist und wie er verwendet werden kann.
quelle
Ich möchte einen weiteren einfachen Vergleich zwischen Python und JS-Beispiel anbieten, wenn dies dazu beiträgt, die Dinge klarer zu machen.
JS:
und Ausführen:
Python:
und Ausführen:
Grund: Wie viele andere oben bereits erwähnt, wird in Python eine neue Referenz im inneren Bereich erstellt, wenn im inneren Bereich eine Zuordnung zu einer Variablen mit demselben Namen erfolgt. Nicht so bei JS, es sei denn, Sie deklarieren ausdrücklich eins mit dem
var
Schlüsselwort.quelle