Die Erklärung
Das Problem hierbei ist, dass der Wert von ibeim Erstellen der Funktion nicht gespeichert fwird. Schlägt vielmehr fden Wert des iAufrufs 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_varnach der Deklaration der Funktion geändert hat. Dasselbe passiert in Ihrem eigenen Code: Zum Zeitpunkt Ihres Aufrufs fhat sich der Wert von igeä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, iindem 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 iund 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 ieines Verschlusses zu erfassen
Die Wurzel Ihres Problems ist, dass isich 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.partialdiese Option , um den aktuellen Wert von ian zu bindenf
functools.partialMit 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 inoch geändert hat, obwohl wir daraus ein Standardargument gemacht haben! Wenn Ihr Code mutiert i , müssen Sie eine Kopie davon iwie folgt an Ihre Funktion binden :
def f(i=i.copy()):
f = f_factory(i.copy())
f_with_i = functools.partial(f, i.copy())