Замыкания и параметры по умолчанию в Python
@python_quizРазберем этот квиз
Коротко: в примере ключевой момент — как лямбда-функции захватывают переменные из окружения и как параметр по умолчанию фиксирует значение на момент создания функции.
Исходный код

Что тут происходит на каждом шаге:
- Цикл проходит по
i = 0, 1, 2. - При создании каждой лямбды используется параметр по умолчанию
i=i. Это запоминает текущее значениеiвнутри самой функции. - После цикла
funcsсодержит три функций, каждая с «своим» зафиксированным значением i (0, 1, 2). - Вызов
f(10)для каждой функции возвращает10 + i, то есть итоговый список будет [10, 11, 12].
Почему работает именно так (пояснение)
В Python замыкания захватывают переменные по ссылке, не по значению. Если бы мы написали лямбду без параметра по умолчанию:

то все три лямбды использовали бы одну и ту же внешнюю переменную i. После завершения цикла i равно 2, поэтому все вызовы вернули бы 12 — результат позднего связывания (late binding).
Параметр по умолчанию (i=i) создаёт локальную переменную в момент определения функции и присваивает ей текущее значение внешней i. Таким образом, значение фиксируется «по месту и времени» создания функции.
Демонстрация и сравнение вариантов
Оригинальный код (фиксирует i):

Без фиксации (позднее связывание):

Альтернативный способ фиксации значения — фабрика функций:

Ещё вариант с functools.partial:

Анализ вариантов ответов (какие выводы соответствуют каким случаям)
- Вывод вида [10, 10, 10] мог бы возникнуть только при ошибочной фиксации и одинаковом
i=0для всех функций (например, если вручную присвоитьi=0везде). - Вывод [10, 11, 12] — результат фиксации значения i при создании каждой лямбды (через
i=iили фабрику функций). - Вывод [12, 12, 12] — типичный результат, когда лямбды захватывают внешнюю переменную без фиксации (все используют финальное значение
i=2). - Вывод [13, 13, 13] соответствовал бы случаю, если бы финальное значение i было 3 и все функции ссылались на него (например, range(3) и затем i==3 в других контекстах), но в данном коде это не происходит.
Выводы и рекомендации
- Если вам нужны независимые захваченные значения в замыканиях, фиксируйте их при создании функции — параметр по умолчанию (
lambda x, i=i: ...) — простой и идиоматичный способ. - Альтернативы: создать фабрику функций (def make(i): return lambda x: ...) или использовать functools.partial в подходящих случаях.
- Помните: замыкания в Python по умолчанию связывают имя, а не значение — это частая ловушка для начинающих.
Если хотите, могу показать ещё примеры с более сложными циклами (вложенные циклы, объекты и т.п.) или перевести это поведение на асинхронные сценарии — скажите, что интересует.