Почему [2, 2, 2]? Замыкания и лямбды в цикле Python
@python_quizРазберем этот квиз
Коротко: приведённый код печатает [2, 2, 2]. Ниже — почему это происходит и как правильно получить ожидаемое поведение, если вы хотите «захватить» текущее значение итератора.
Условие (код из квиза)

Если запустить — вывод будет:

Почему так происходит: позднее связывание (late binding)
Лямбда-функции (как и обычные вложенные функции) захватывают переменные по ссылке, а не по значению. Переменная i не копируется во время создания каждой лямбды — все три лямбды ссылаются на одну и ту же переменную i. Значение i меняется в цикле и после завершения цикла равно 2 (последнее значение range(3)). При вызове каждой лямбды происходит чтение текущего значения i — то есть 2.
Проще: лямбда «знает», откуда взять i, но не «запомнила» его значение во время итерации.
Как получить ожидаемое поведение (раннее связывание)
Есть несколько простых способов привязать текущее значение i к замыканию сразу при создании функции.
1) Использовать аргумент по умолчанию у лямбды

Аргумент i=i вычисляется в момент создания лямбды, поэтому значение фиксируется.
2) Генератор (list comprehension) с аргументом по умолчанию

3) Фабрика функций (явное замыкание через вложенную функцию)

4) Использовать functools.partial (парадигма отличается — привязываем аргумент функции)

Обратите внимание: в этом примере порядок аргументов в mul подобран так, чтобы partial фиксировал сначала i.
Краткие рекомендации и инсайты
- Поведение, которое вы видите, — стандартное и ожидаемое в CPython и других реализациях Python: замыкания захватывают переменные, а не их текущие значения.
- Самый компактный и часто используемый прием — аргумент по умолчанию в лямбде: lambda x,
i=i: ... - Для читаемости и тестируемости иногда лучше использовать явную фабрику функций (def make_multiplier(...)).
- Если нужно фиксировать несколько переменных, можно перечислить их как аргументы по умолчанию: lambda x,
a=a,b=b: ...
Итог
Если вы видите похожую неожиданность при создании функций в цикле — вспомните про позднее связывание и примените одно из решений выше, чтобы «запомнить» текущее значение переменной в момент создания функции.