Ответ на вопрос
Разбор поведения
Исходный фрагмент:
for i in range(5):
funcs = []
funcs.append(lambda: i)
print([f() for f in funcs])
Что происходит и почему:
- В этом коде список `funcs` создаётся внутри цикла и сразу содержит одну функцию, которая захватывает имя `i`.
- Эта функция вызывается немедленно в той же итерации, поэтому она возвращает текущее значение `i`.
- Поэтому вывод будет: \([0]\), \([1]\), \([2]\), \([3]\), \([4]\) — по одному элементу в каждой итерации.
Типичная проблема замыканий (которая, вероятно, имелась в виду) проявляется, если вы хотите собрать все функции в список и вызвать их после цикла. Например:
funcs = []
for i in range(5):
funcs.append(lambda: i)
print([f() for f in funcs])
В этом случае все функции захватывают одно и то же имя `i` (late binding), и при вызове после цикла все вернут последнее значение `i` — \([4,4,4,4,4]\).
Как исправить, чтобы каждая функция «захватила» своё значение `i`:
1) Использовать параметр по умолчанию у lambda (самый простой):
funcs = []
for i in range(5):
funcs.append(lambda i=i: i)
print([f() for f in funcs]) # -> [0, 1, 2, 3, 4]
2) Использовать фабрику функций:
def make(i):
return lambda: i
funcs = [make(i) for i in range(5)]
print([f() for f in funcs]) # -> [0, 1, 2, 3, 4]
3) Использовать functools.partial:
from functools import partial
funcs = []
for i in range(5):
funcs.append(partial(lambda x: x, i))
print([f() for f in funcs]) # -> [0, 1, 2, 3, 4]
Короткое объяснение механизма: lambda захватывает ссылку на переменную `i`, а не её текущее значение; использование `i=i` или явной фабрики создаёт новую локальную переменную со значением на момент создания, тем самым фиксируя его.
Еще