Die Erklärung
Das Problem hierbei ist, dass der Wert von i
beim Erstellen der Funktion nicht gespeichert f
wird. Schlägt vielmehr f
den Wert des i
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:
global_var = 'foo'
def my_function():
print(global_var)
global_var = 'bar'
my_function()
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 Aufrufs f
hat sich der Wert von i
geändert und wurde auf gesetzt 2
.
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 verwenden
Im Gegensatz zu Abschlussvariablen (wie i
) werden Standardargumente sofort ausgewertet, wenn die Funktion definiert wird:
for i in range(3):
def f(i=i): # <- right here is the important bit
return i
functions.append(f)
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.
>>> i = 0
>>> def f(i=i):
... pass
>>> f.__defaults__ # this is where the current value of i is stored
(0,)
>>> # assigning a new value to i has no effect on the function's default arguments
>>> i = 5
>>> f.__defaults__
(0,)
Verwenden Sie eine Funktionsfactory, um den aktuellen Wert i
eines Verschlusses zu erfassen
Die 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 :
def f_factory(i):
def f():
return i # i is now a *local* variable of f_factory and can't ever change
return f
for i in range(3):
f = f_factory(i)
functions.append(f)
Verwenden Sie functools.partial
diese Option , um den aktuellen Wert von i
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.
import functools
def f(i):
return i
for i in range(3):
f_with_i = functools.partial(f, i) # important: use a different variable than "f"
functions.append(f_with_i)
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:
>>> i = [] # instead of an int, i is now a *mutable* object
>>> def f(i=i):
... print('i =', i)
...
>>> i.append(5) # instead of *assigning* a new value to i, we're *mutating* it
>>> f()
i = [5]
Beachten Sie, wie sich i
noch geändert hat, obwohl wir daraus ein Standardargument gemacht haben! Wenn Ihr Code mutiert i
, müssen Sie eine Kopie davon i
wie folgt an Ihre Funktion binden :
def f(i=i.copy()):
f = f_factory(i.copy())
f_with_i = functools.partial(f, i.copy())