Объяснение
Проблема здесь в том, что значение iне сохраняется при создании функции f. Скорее, fищет значение iпри его вызове .
Если задуматься, такое поведение имеет смысл. Фактически, это единственный разумный способ работы функций. Представьте, что у вас есть функция, которая обращается к глобальной переменной, например:
global_var = 'foo'
def my_function():
print(global_var)
global_var = 'bar'
my_function()
Когда вы читаете этот код, вы, конечно, ожидаете, что он напечатает «bar», а не «foo», потому что значение global_varизменилось после того, как функция была объявлена. То же самое происходит в вашем собственном коде: к тому времени, когда вы вызываете f, значение iизменилось и было установлено на 2.
Решение
На самом деле есть много способов решить эту проблему. Вот несколько вариантов:
Принудительное раннее связывание i, используя его как аргумент по умолчанию
В отличие от закрывающих переменных (например, i), аргументы по умолчанию оцениваются сразу после определения функции:
for i in range(3):
def f(i=i):
return i
functions.append(f)
Чтобы немного понять, как и почему это работает: аргументы функции по умолчанию хранятся как атрибут функции; таким образом, текущее значение iснимается и сохраняется.
>>> i = 0
>>> def f(i=i):
... pass
>>> f.__defaults__
(0,)
>>>
>>> i = 5
>>> f.__defaults__
(0,)
Используйте фабрику функций для захвата текущего значения iв замыкании
Корень вашей проблемы в том, что iпеременная может изменяться. Мы можем обойти эту проблему, создав еще одну переменную, которая гарантированно никогда не изменится, и самый простой способ сделать это - закрыть :
def f_factory(i):
def f():
return i
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)
functions.append(f_with_i)
Предупреждение: эти решения работают, только если вы присвоите переменной новое значение. Если вы измените объект, хранящийся в переменной, вы снова столкнетесь с той же проблемой:
>>> i = []
>>> def f(i=i):
... print('i =', i)
...
>>> i.append(5)
>>> f()
i = [5]
Обратите внимание, как iвсе еще изменилось, хотя мы превратили его в аргумент по умолчанию! Если ваш код мутирует i , то вы должны связать копию из iвашей функции, например , так:
def f(i=i.copy()):
f = f_factory(i.copy())
f_with_i = functools.partial(f, i.copy())