Vor kurzem habe ich angefangen, mit Python herumzuspielen, und bin auf etwas Besonderes gestoßen, wie Verschlüsse funktionieren. Betrachten Sie den folgenden Code:
adders=[0,1,2,3]
for i in [0,1,2,3]:
adders[i]=lambda a: i+a
print adders[1](3)
Es wird ein einfaches Array von Funktionen erstellt, die eine einzelne Eingabe verwenden und die durch eine Zahl hinzugefügte Eingabe zurückgeben. Die Funktionen sind in einer for
Schleife aufgebaut, in der der Iterator i
von 0
bis ausgeführt wird 3
. Für jede dieser Zahlen wird eine lambda
Funktion erstellt, die sie erfasst i
und zur Eingabe der Funktion hinzufügt. Die letzte Zeile ruft die zweite lambda
Funktion mit 3
als Parameter auf. Zu meiner Überraschung war die Ausgabe 6
.
Ich habe a erwartet 4
. Meine Argumentation war: In Python ist alles ein Objekt und daher ist jede Variable ein wesentlicher Zeiger darauf. Beim Erstellen der lambda
Closures für habe i
ich erwartet, dass ein Zeiger auf das Integer-Objekt gespeichert wird, auf das aktuell verwiesen wird i
. Das bedeutet, dass beim i
Zuweisen eines neuen Ganzzahlobjekts die zuvor erstellten Abschlüsse nicht beeinflusst werden sollten. Leider zeigt die Überprüfung des adders
Arrays in einem Debugger, dass dies der Fall ist. Alle lambda
Funktionen beziehen sich auf den letzten Wert i
, zeigt 3
, deren Ergebnisse in der adders[1](3)
Rückkehr 6
.
Was mich über Folgendes wundern lässt:
- Was erfassen die Verschlüsse genau?
- Was ist die eleganteste Methode, um die
lambda
Funktionen davon zu überzeugen , den aktuellen Wert voni
auf eine Weise zu erfassen , die nicht beeinflusst wird, wenni
sich ihr Wert ändert?
i
verlässt man den Namespace?print i
das nach der Schleife nicht funktionieren würde. Aber ich habe es selbst getestet und jetzt verstehe ich, was du meinst - es funktioniert. Ich hatte keine Ahnung, dass Schleifenvariablen nach dem Schleifenkörper in Python verweilten.if
,with
,try
usw.Antworten:
Ihre zweite Frage wurde beantwortet, aber Ihre erste:
Das Scoping in Python ist
dynamisch undlexikalisch. Ein Abschluss merkt sich immer den Namen und den Umfang der Variablen, nicht das Objekt, auf das sie zeigt. Da alle Funktionen in Ihrem Beispiel im selben Bereich erstellt wurden und denselben Variablennamen verwenden, beziehen sie sich immer auf dieselbe Variable.EDIT: In Bezug auf Ihre andere Frage, wie Sie dies überwinden können, gibt es zwei Möglichkeiten, die Ihnen in den Sinn kommen:
Der prägnanteste, aber nicht genau gleichwertige Weg ist der von Adrien Plisson empfohlene . Erstellen Sie ein Lambda mit einem zusätzlichen Argument und setzen Sie den Standardwert des zusätzlichen Arguments auf das Objekt, das beibehalten werden soll.
Etwas ausführlicher, aber weniger hackig wäre es, jedes Mal, wenn Sie das Lambda erstellen, einen neuen Bereich zu erstellen:
Der Bereich hier wird mithilfe einer neuen Funktion (der Kürze halber ein Lambda) erstellt, die das Argument bindet und den Wert, den Sie binden möchten, als Argument übergibt. In echtem Code haben Sie jedoch höchstwahrscheinlich eine normale Funktion anstelle des Lambda, um den neuen Bereich zu erstellen:
quelle
set!
. Hier erfahren Sie, was dynamischer Bereich wirklich ist: voidspace.org.uk/python/articles/code_blocks.shtml .Sie können die Erfassung einer Variablen mithilfe eines Arguments mit einem Standardwert erzwingen:
Die Idee ist, einen Parameter (geschickt benannt
i
) zu deklarieren und ihm einen Standardwert der Variablen zu geben, die Sie erfassen möchten (den Wert voni
).quelle
Der Vollständigkeit halber eine weitere Antwort auf Ihre zweite Frage: Sie können teilweise im functools- Modul verwenden.
Beim Importieren von Add from Operator, wie von Chris Lutz vorgeschlagen, wird das Beispiel wie folgt:
quelle
Betrachten Sie den folgenden Code:
Ich denke, die meisten Leute werden das überhaupt nicht verwirrend finden. Es ist das erwartete Verhalten.
Warum denken die Leute, dass es anders wäre, wenn es in einer Schleife gemacht wird? Ich weiß, dass ich diesen Fehler selbst gemacht habe, aber ich weiß nicht warum. Es ist die Schleife? Oder vielleicht das Lambda?
Immerhin ist die Schleife nur eine kürzere Version von:
quelle
i
für jede Lambda-Funktion auf dieselbe Variable zugegriffen wird.Als Antwort auf Ihre zweite Frage besteht die eleganteste Möglichkeit darin, eine Funktion zu verwenden, die zwei Parameter anstelle eines Arrays verwendet:
Allerdings ist die Verwendung von Lambda hier etwas albern. Python gibt uns das
operator
Modul, das eine funktionale Schnittstelle zu den Basisoperatoren bietet. Das obige Lambda hat unnötigen Overhead, nur um den Additionsoperator aufzurufen:Ich verstehe, dass Sie herumspielen und versuchen, die Sprache zu erkunden, aber ich kann mir keine Situation vorstellen, in der ich eine Reihe von Funktionen verwenden würde, bei denen Pythons Scoping-Verrücktheit im Weg stehen würde.
Wenn Sie möchten, können Sie eine kleine Klasse schreiben, die Ihre Array-Indizierungssyntax verwendet:
quelle
Hier ist ein neues Beispiel, das die Datenstruktur und den Inhalt eines Abschlusses hervorhebt, um zu verdeutlichen, wann der umschließende Kontext "gespeichert" wird.
Was ist in einer Schließung?
Insbesondere befindet sich my_str nicht im Abschluss von f1.
Was ist in der Schließung von f2?
Beachten Sie (aus den Speicheradressen), dass beide Verschlüsse dieselben Objekte enthalten. Sie können sich also vorstellen , dass die Lambda-Funktion einen Verweis auf den Bereich enthält. My_str befindet sich jedoch nicht im Abschluss für f_1 oder f_2, und i befindet sich nicht im Abschluss für f_3 (nicht gezeigt), was darauf hindeutet, dass die Abschlussobjekte selbst unterschiedliche Objekte sind.
Sind die Verschlussobjekte selbst dasselbe Objekt?
quelle
int object at [address X]>
ließ mich denken, dass der Abschluss [Adresse X] AKA als Referenz speichert. [Adresse X] ändert sich jedoch, wenn die Variable nach der Lambda-Anweisung neu zugewiesen wird.