Während ich ein Problem untersuchte, das ich mit lexikalischen Abschlüssen in Javascript-Code hatte, stieß ich in Python auf dieses Problem:
flist = []
for i in xrange(3):
def func(x): return x * i
flist.append(func)
for f in flist:
print f(2)
Beachten Sie, dass dieses Beispiel achtsam vermeidet lambda
. Es druckt "4 4 4", was überraschend ist. Ich würde "0 2 4" erwarten.
Dieser äquivalente Perl-Code macht es richtig:
my @flist = ();
foreach my $i (0 .. 2)
{
push(@flist, sub {$i * $_[0]});
}
foreach my $f (@flist)
{
print $f->(2), "\n";
}
"0 2 4" wird gedruckt.
Können Sie bitte den Unterschied erklären?
Aktualisieren:
Das Problem ist nicht mit i
global sein. Dies zeigt das gleiche Verhalten:
flist = []
def outer():
for i in xrange(3):
def inner(x): return x * i
flist.append(inner)
outer()
#~ print i # commented because it causes an error
for f in flist:
print f(2)
Wie die kommentierte Zeile zeigt, i
ist zu diesem Zeitpunkt unbekannt. Trotzdem wird "4 4 4" gedruckt.
python
closures
lazy-evaluation
late-binding
Eli Bendersky
quelle
quelle
Antworten:
Python verhält sich tatsächlich wie definiert. Es werden drei separate Funktionen erstellt, die jedoch jeweils die Umgebung schließen, in der sie definiert sind - in diesem Fall die globale Umgebung (oder die Umgebung der äußeren Funktion, wenn die Schleife in einer anderen Funktion platziert ist). Dies ist jedoch genau das Problem - in dieser Umgebung ist ich mutiert und die Verschlüsse beziehen sich alle auf dasselbe i .
Hier ist die beste Lösung, die ich finden kann - erstellen Sie einen Funktionsersteller und rufen Sie diesen stattdessen auf. Dies erzwingt unterschiedliche Umgebungen für jede der erstellten Funktionen mit jeweils einem anderen i .
Dies passiert, wenn Sie Nebenwirkungen und funktionale Programmierung mischen.
quelle
def inner(x, i=i): return x * i
Die in der Schleife definierten Funktionen greifen weiterhin auf dieselbe Variable zu,
i
während sich ihr Wert ändert. Am Ende der Schleife zeigen alle Funktionen auf dieselbe Variable, die den letzten Wert in der Schleife enthält: Der Effekt ist der im Beispiel angegebene.Um
i
seinen Wert auszuwerten und zu verwenden, wird er häufig als Parameterstandard festgelegt: Parameterstandards werden ausgewertet, wenn diedef
Anweisung ausgeführt wird, und daher wird der Wert der Schleifenvariablen eingefroren.Folgendes funktioniert wie erwartet:
quelle
def
Anweisung ausgeführt wird /i
aus der Definition überschrieben . :-(So machen Sie es mit der
functools
Bibliothek (von der ich nicht sicher bin, ob sie zum Zeitpunkt der Fragestellung verfügbar war).Gibt erwartungsgemäß 0 2 4 aus.
quelle
functools.partialmethod()
ab Python 3.4Schau dir das an:
Dies bedeutet, dass alle auf dieselbe i-Variableninstanz verweisen, die nach Beendigung der Schleife den Wert 2 hat.
Eine lesbare Lösung:
quelle
Was passiert ist, dass die Variable i erfasst wird und die Funktionen den Wert zurückgeben, an den sie zum Zeitpunkt des Aufrufs gebunden ist. In funktionalen Sprachen tritt diese Art von Situation nie auf, da ich nicht zurückprallen würde. Bei Python und auch bei Lisp ist dies jedoch nicht mehr der Fall.
Der Unterschied zu Ihrem Schema-Beispiel besteht in der Semantik der do-Schleife. Das Schema erstellt effektiv jedes Mal eine neue i-Variable durch die Schleife, anstatt eine vorhandene i-Bindung wie bei den anderen Sprachen wiederzuverwenden. Wenn Sie eine andere Variable verwenden, die außerhalb der Schleife erstellt und mutiert wurde, wird im Schema dasselbe Verhalten angezeigt. Versuchen Sie, Ihre Schleife durch Folgendes zu ersetzen:
Schauen Sie hier, um weitere Diskussionen darüber zu führen.
[Bearbeiten] Möglicherweise ist es besser, sich die do-Schleife als ein Makro vorzustellen, das die folgenden Schritte ausführt:
dh. das Äquivalent zu der folgenden Python:
Das i ist nicht mehr das aus dem übergeordneten Bereich, sondern eine brandneue Variable in ihrem eigenen Bereich (dh der Parameter für das Lambda), sodass Sie das beobachtete Verhalten erhalten. Python hat diesen impliziten neuen Bereich nicht, daher teilt der Hauptteil der for-Schleife nur die Variable i.
quelle
Ich bin immer noch nicht ganz davon überzeugt, warum dies in einigen Sprachen so und auf andere Weise funktioniert. In Common Lisp ist es wie in Python:
Druckt "6 6 6" (beachten Sie, dass hier die Liste von 1 bis 3 ist und umgekehrt aufgebaut ist "). Während in Schema funktioniert es wie in Perl:
Druckt "6 4 2"
Und wie ich bereits erwähnt habe, befindet sich Javascript im Python / CL-Camp. Es scheint, dass es hier eine Implementierungsentscheidung gibt, die verschiedene Sprachen auf unterschiedliche Weise angehen. Ich würde gerne genau verstehen, was die Entscheidung ist.
quelle
Das Problem ist, dass alle lokalen Funktionen an dieselbe Umgebung und damit an dieselbe
i
Variable gebunden sind. Die Lösung (Problemumgehung) besteht darin, für jede Funktion (oder jedes Lambda) separate Umgebungen (Stapelrahmen) zu erstellen:quelle
Die Variable
i
ist eine globale Variable , deren Wert bei jedemf
Aufruf der Funktion 2 beträgt.Ich wäre geneigt, das Verhalten, nach dem Sie suchen, wie folgt umzusetzen:
Antwort auf Ihr Update : Es ist nicht die Globalität an
i
sich, die dieses Verhalten verursacht, sondern die Tatsache, dass es sich um eine Variable aus einem umschließenden Bereich handelt, die über die Zeiten, in denen f aufgerufen wird, einen festen Wert hat. In Ihrem zweiten Beispiel wird der Wert voni
aus demkkk
Funktionsumfang übernommen, und daran ändert sich nichts, wenn Sie die Funktionen aufrufenflist
.quelle
Die Gründe für das Verhalten wurden bereits erklärt und es wurden mehrere Lösungen veröffentlicht, aber ich denke, dies ist die pythonischste (denken Sie daran, alles in Python ist ein Objekt!):
Claudius Antwort ist ziemlich gut, wenn man einen Funktionsgenerator verwendet, aber Piros Antwort ist ein Hack, um ehrlich zu sein, da es mich zu einem "versteckten" Argument mit einem Standardwert macht (es wird gut funktionieren, aber es ist nicht "pythonisch"). .
quelle
func
Inx * func.i
bezieht sich immer auf die zuletzt definierte Funktion. Obwohl jede Funktion einzeln die richtige Nummer hat, lesen sie trotzdem alle von der letzten.