Wyjaśnienie
Problem polega na tym, że wartość inie jest zapisywana podczas tworzenia funkcji f. Raczej fwyszukuje wartość, ikiedy jest wywoływana .
Jeśli się nad tym zastanowić, to zachowanie ma sens. W rzeczywistości jest to jedyny rozsądny sposób działania funkcji. Wyobraź sobie, że masz funkcję, która uzyskuje dostęp do zmiennej globalnej, na przykład:
global_var = 'foo'
def my_function():
print(global_var)
global_var = 'bar'
my_function()
Czytając ten kod, spodziewalibyśmy się - oczywiście - wypisać "bar", a nie "foo", ponieważ wartość global_varzmieniła się po zadeklarowaniu funkcji. To samo dzieje się w Twoim własnym kodzie: w momencie wywołania fwartość zmiennej izmieniła się i została ustawiona na2 .
Rozwiązanie
W rzeczywistości istnieje wiele sposobów rozwiązania tego problemu. Oto kilka opcji:
Wymuś wczesne wiązanie i, używając go jako argumentu domyślnego
W przeciwieństwie do zmiennych zamykających (takich jak i), argumenty domyślne są obliczane natychmiast po zdefiniowaniu funkcji:
for i in range(3):
def f(i=i):
return i
functions.append(f)
Aby dać trochę wglądu w to, jak / dlaczego to działa: Domyślne argumenty funkcji są przechowywane jako atrybut funkcji; Więc bieżąca wartość ijest zapisywana i zapisywana.
>>> i = 0
>>> def f(i=i):
... pass
>>> f.__defaults__
(0,)
>>>
>>> i = 5
>>> f.__defaults__
(0,)
Użyj fabryki funkcji, aby uchwycić bieżącą wartość izamknięcia
Przyczyną twojego problemu jest to, że izmienna może się zmieniać. Możemy obejść ten problem, tworząc kolejną zmienną, która na pewno nigdy się nie zmieni - a najłatwiejszym sposobem na to jest zamknięcie :
def f_factory(i):
def f():
return i
return f
for i in range(3):
f = f_factory(i)
functions.append(f)
Posługiwać się functools.partial związać bieżącą wartość idof
functools.partialumożliwia dołączenie argumentów do istniejącej funkcji. W pewnym sensie jest to też rodzaj fabryki funkcji.
import functools
def f(i):
return i
for i in range(3):
f_with_i = functools.partial(f, i)
functions.append(f_with_i)
Uwaga: te rozwiązania działają tylko wtedy, gdy przypiszesz nową wartość do zmiennej. Jeśli ty zmodyfikujesz obiekt przechowywany w zmiennej, ponownie wystąpi ten sam problem:
>>> i = []
>>> def f(i=i):
... print('i =', i)
...
>>> i.append(5)
>>> f()
i = [5]
Zwróć uwagę, jak ito się zmieniło, mimo że zmieniliśmy go w argument domyślny! Jeśli twój kod ulega mutacji i , musisz powiązać kopię ze iswoją funkcją, na przykład:
def f(i=i.copy()):
f = f_factory(i.copy())
f_with_i = functools.partial(f, i.copy())