Пояснення
Проблема тут полягає в тому, що значення параметра iне зберігається під час створення функції f. Швидше, fшукає значення, iколи воно викликається .
Якщо ви задумаєтесь, така поведінка має ідеальний сенс. Насправді це єдиний розумний спосіб роботи функцій. Уявіть, що у вас є функція, яка отримує доступ до глобальної змінної, наприклад:
global_var = 'foo'
def my_function():
print(global_var)
global_var = 'bar'
my_function()
Читаючи цей код, ви, звичайно, очікуєте, що він надрукує "бар", а не "foo", тому що значення global_varпісля зміни функції було змінено. Те ж саме відбувається у вашому власному коді: На момент виклику fзначення параметра iзмінилося та було встановлено на2 .
Рішення
Насправді існує багато способів вирішити цю проблему. Ось кілька варіантів:
Примусове раннє прив’язування i, використовуючи його як аргумент за замовчуванням
На відміну від змінних закриття (типу i), аргументи за замовчуванням оцінюються негайно, коли функція визначена:
for i in range(3):
def f(i=i): # <- right here is the important bit
return i
functions.append(f)
Щоб трохи зрозуміти, як / чому це працює: Аргументи за замовчуванням функції зберігаються як атрибут функції; таким чином поточне значення iоновлюється та зберігається.
>>> 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,)
Використовуйте фабрику функцій для збору поточного значення iзамикання
Корінь вашої проблеми полягає в тому, що iце змінна, яка може змінюватися. Ми можемо подолати цю проблему, створивши іншу змінну, яка гарантовано ніколи не зміниться - і найпростіший спосіб зробити це - закриття :
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)
Використовуйте functools.partialдля прив'язки поточного значення iдоf
functools.partialдозволяє додавати аргументи до існуючої функції. Певним чином, це теж свого роду фабрика функцій.
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)
Caveat: Ці рішення працюють лише в тому випадку, якщо ви присвоїли нове значення змінній. Якщо ви зміните об'єкт, що зберігається у змінній, ви знову відчуєте ту ж проблему:
>>> 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]
Зауважте, як iвсе-таки змінилося, хоча ми перетворили його на аргумент за замовчуванням! Якщо ваш код мутує i , то ви повинні зв'язати копію з iвашої функції, наприклад , так:
def f(i=i.copy()):
f = f_factory(i.copy())
f_with_i = functools.partial(f, i.copy())