Ich versuche, Funktionen innerhalb einer Schleife zu erstellen:
functions = []
for i in range(3):
def f():
return i
# alternatively: f = lambda: i
functions.append(f)
Das Problem ist, dass alle Funktionen gleich sind. Anstatt 0, 1 und 2 zurückzugeben, geben alle drei Funktionen 2 zurück:
print([f() for f in functions])
# expected output: [0, 1, 2]
# actual output: [2, 2, 2]
Warum passiert das und was soll ich tun, um 3 verschiedene Funktionen zu erhalten, die jeweils 0, 1 und 2 ausgeben?
Antworten:
Sie haben ein Problem mit der späten Bindung - jede Funktion wird
i
so spät wie möglich angezeigt (wenn sie nach dem Ende der Schleife aufgerufen wird,i
wird sie auf gesetzt2
).Einfach durch Erzwingen einer frühen Bindung
def f():
zu beheben: Ändern Sie diesdef f(i=i):
wie folgt:Die Standardwerte (die rechte
i
ini=i
ein Standardwert für Argumentnameni
, die die linke isti
ini=i
) an nachgeschlagendef
Zeit, nicht an dercall
Zeit, so im Wesentlichen sind sie einen Weg , um gezielt nach frühe Bindung.Wenn Sie sich Sorgen machen
f
, ein zusätzliches Argument zu erhalten (und daher möglicherweise fälschlicherweise aufgerufen zu werden), gibt es eine ausgefeiltere Methode, bei der ein Verschluss als "Funktionsfactory" verwendet wird:und in Ihrer Schleife
f = make_f(i)
anstelle derdef
Anweisung verwenden.quelle
Die Erklärung
Das Problem hierbei ist, dass der Wert von
i
beim Erstellen der Funktion nicht gespeichertf
wird. Schlägt vielmehrf
den Wert desi
Aufrufs nach .Wenn Sie darüber nachdenken, ist dieses Verhalten absolut sinnvoll. Tatsächlich ist dies die einzig vernünftige Art und Weise, wie Funktionen funktionieren können. Stellen Sie sich vor, Sie haben eine Funktion, die auf eine globale Variable zugreift, wie folgt:
Wenn Sie diesen Code lesen, würden Sie natürlich erwarten, dass er "bar" und nicht "foo" druckt, da sich der Wert von
global_var
nach der Deklaration der Funktion geändert hat. Dasselbe passiert in Ihrem eigenen Code: Zum Zeitpunkt Ihres Aufrufsf
hat sich der Wert voni
geändert und wurde auf gesetzt2
.Die Lösung
Es gibt tatsächlich viele Möglichkeiten, dieses Problem zu lösen. Hier sind einige Optionen:
Erzwingen Sie eine frühzeitige Bindung,
i
indem Sie sie als Standardargument verwendenIm Gegensatz zu Abschlussvariablen (wie
i
) werden Standardargumente sofort ausgewertet, wenn die Funktion definiert wird:Um einen kleinen Einblick zu geben, wie / warum dies funktioniert: Die Standardargumente einer Funktion werden als Attribut der Funktion gespeichert. Somit wird der aktuelle Wert von aufgenommen
i
und gespeichert.Verwenden Sie eine Funktionsfactory, um den aktuellen Wert
i
eines Verschlusses zu erfassenDie Wurzel Ihres Problems ist, dass
i
sich eine Variable ändern kann. Wir können dieses Problem umgehen, indem wir eine weitere Variable erstellen , die sich garantiert nie ändert - und der einfachste Weg, dies zu tun, ist ein Abschluss :Verwenden Sie
functools.partial
diese Option , um den aktuellen Wert voni
an zu bindenf
functools.partial
Mit dieser Option können Sie einer vorhandenen Funktion Argumente hinzufügen. In gewisser Weise ist es auch eine Art Funktionsfabrik.Vorsichtsmaßnahme: Diese Lösungen funktionieren nur, wenn Sie der Variablen einen neuen Wert zuweisen . Wenn Sie das in der Variablen gespeicherte Objekt ändern , tritt erneut dasselbe Problem auf:
Beachten Sie, wie sich
i
noch geändert hat, obwohl wir daraus ein Standardargument gemacht haben! Wenn Ihr Code mutierti
, müssen Sie eine Kopie davoni
wie folgt an Ihre Funktion binden :def f(i=i.copy()):
f = f_factory(i.copy())
f_with_i = functools.partial(f, i.copy())
quelle