Ich benötige eine Rückruffunktion, die für eine Reihe von GUI-Ereignissen fast identisch ist. Die Funktion verhält sich etwas anders, je nachdem, welches Ereignis sie aufgerufen hat. Scheint mir ein einfacher Fall zu sein, aber ich kann dieses seltsame Verhalten von Lambda-Funktionen nicht herausfinden.
Ich habe also den folgenden vereinfachten Code unten:
def callback(msg):
print msg
#creating a list of function handles with an iterator
funcList=[]
for m in ('do', 're', 'mi'):
funcList.append(lambda: callback(m))
for f in funcList:
f()
#create one at a time
funcList=[]
funcList.append(lambda: callback('do'))
funcList.append(lambda: callback('re'))
funcList.append(lambda: callback('mi'))
for f in funcList:
f()
Die Ausgabe dieses Codes lautet:
mi
mi
mi
do
re
mi
Ich erwartete:
do
re
mi
do
re
mi
Warum hat die Verwendung eines Iterators die Dinge durcheinander gebracht?
Ich habe versucht, eine Deepcopy zu verwenden:
import copy
funcList=[]
for m in ('do', 're', 'mi'):
funcList.append(lambda: callback(copy.deepcopy(m)))
for f in funcList:
f()
Das hat aber das gleiche Problem.
python
lexical-closures
Agartland
quelle
quelle
Antworten:
Das Problem hierbei ist, dass die
m
Variable (eine Referenz) aus dem umgebenden Bereich entnommen wird. Im Lambda-Bereich werden nur Parameter gespeichert.Um dies zu lösen, müssen Sie einen anderen Bereich für Lambda erstellen:
Im obigen Beispiel verwendet Lambda auch den umgebenden Bereich, um zu suchen
m
, aber dieses Mal wird diesercallback_factory
Bereich einmal procallback_factory
Aufruf erstellt.Oder mit functools.partial :
quelle
Wenn ein Lambda erstellt wird, wird keine Kopie der Variablen in dem von ihm verwendeten umschließenden Bereich erstellt. Es wird ein Verweis auf die Umgebung beibehalten, damit der Wert der Variablen später nachgeschlagen werden kann. Es gibt nur einen
m
. Es wird jedes Mal durch die Schleife zugewiesen. Nach der Schleife hat die Variablem
einen Wert'mi'
. Wenn Sie also die später erstellte Funktion tatsächlich ausführen, wird der Wertm
in der Umgebung, in der sie erstellt wurde, nachgeschlagen, die dann einen Wert hat'mi'
.Eine übliche und idiomatische Lösung für dieses Problem besteht darin, den Wert
m
zum Zeitpunkt der Erstellung des Lambda zu erfassen, indem es als Standardargument eines optionalen Parameters verwendet wird. Normalerweise verwenden Sie einen gleichnamigen Parameter, damit Sie den Code nicht ändern müssen:quelle
Python verwendet natürlich Referenzen, aber das spielt in diesem Zusammenhang keine Rolle.
Wenn Sie ein Lambda (oder eine Funktion, da dies genau dasselbe Verhalten ist) definieren, wird der Lambda-Ausdruck vor der Laufzeit nicht ausgewertet:
Noch überraschender als Ihr Lambda-Beispiel:
Kurz gesagt, denken Sie dynamisch: Vor der Interpretation wird nichts ausgewertet. Deshalb verwendet Ihr Code den neuesten Wert von m.
Wenn in der Lambda-Ausführung nach m gesucht wird, wird m aus dem obersten Bereich genommen, was bedeutet, dass, wie andere betonten; Sie können dieses Problem umgehen, indem Sie einen weiteren Bereich hinzufügen:
Wenn hier das Lambda aufgerufen wird, sucht es im Definitionsbereich des Lambda nach einem x. Dieses x ist eine lokale Variable, die im Fabrikkörper definiert ist. Aus diesem Grund ist der Wert, der bei der Lambda-Ausführung verwendet wird, der Wert, der beim Aufruf der Factory als Parameter übergeben wurde. Und Doremi!
Als Hinweis hätte ich Factory als Factory (m) definieren können [x durch m ersetzen], das Verhalten ist das gleiche. Ich habe aus Gründen der Klarheit einen anderen Namen verwendet :)
Sie könnten feststellen, dass Andrej Bauer ähnliche Lambda-Probleme hatte. Was in diesem Blog interessant ist, sind die Kommentare, in denen Sie mehr über das Schließen von Python erfahren werden :)
quelle
Nicht direkt mit dem vorliegenden Thema verbunden, aber dennoch ein unschätzbares Stück Weisheit: Python Objects von Fredrik Lundh.
quelle
Ja, das ist ein Problem des Umfangs, es bindet an das äußere m, egal ob Sie ein Lambda oder eine lokale Funktion verwenden. Verwenden Sie stattdessen einen Funktor:
quelle
Die Lösung für Lambda ist mehr Lambda
Das Äußere
lambda
wird verwendet, um den aktuellen Wert voni
anj
an zu bindenJedesmal , wenn die äußere
lambda
es genannt wird , macht eine Instanz des innerenlambda
mitj
dem aktuellen Wert von gebundeneni
alsi
‚S - Wertquelle
Erstens ist das, was Sie sehen, kein Problem und bezieht sich nicht auf Call-by-Reference oder By-Value.
Die von Ihnen definierte Lambda-Syntax enthält keine Parameter. Daher liegt der Bereich, den Sie mit den Parametern sehen,
m
außerhalb der Lambda-Funktion. Aus diesem Grund sehen Sie diese Ergebnisse.Die Lambda-Syntax ist in Ihrem Beispiel nicht erforderlich, und Sie möchten lieber einen einfachen Funktionsaufruf verwenden:
Auch hier sollten Sie sehr genau wissen, welche Lambda-Parameter Sie verwenden und wo genau ihr Umfang beginnt und endet.
Als Randnotiz zur Parameterübergabe. Parameter in Python sind immer Verweise auf Objekte. Um Alex Martelli zu zitieren:
quelle
Die Variable
m
wird erfasst, sodass Ihr Lambda-Ausdruck immer seinen "aktuellen" Wert sieht.Wenn Sie den Wert zu einem bestimmten Zeitpunkt effektiv erfassen müssen, schreiben Sie eine Funktion, indem Sie den gewünschten Wert als Parameter verwenden und einen Lambda-Ausdruck zurückgeben. Zu diesem Zeitpunkt erfasst das Lambda den Wert des Parameters , der sich nicht ändert, wenn Sie die Funktion mehrmals aufrufen:
Ausgabe:
quelle
In Python gibt es eigentlich keine Variablen im klassischen Sinne, sondern nur Namen, die durch Verweise auf das betreffende Objekt gebunden wurden. Sogar Funktionen sind in Python eine Art Objekt, und Lambdas machen keine Ausnahme von der Regel :)
quelle
Als Randnotiz
map
erzwingt, obwohl von einer bekannten Python-Figur verachtet, eine Konstruktion, die diese Falle verhindert.NB: Der erste
lambda i
verhält sich wie die Fabrik in anderen Antworten.quelle