Почему [2, 2, 2]? Замыкания и лямбды в цикле Python

Почему [2, 2, 2]? Замыкания и лямбды в цикле Python

@python_quiz

Разберем этот квиз

Коротко: приведённый код печатает [2, 2, 2]. Ниже — почему это происходит и как правильно получить ожидаемое поведение, если вы хотите «захватить» текущее значение итератора.

Условие (код из квиза)

python

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

python

Почему так происходит: позднее связывание (late binding)

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

Проще: лямбда «знает», откуда взять i, но не «запомнила» его значение во время итерации.

Как получить ожидаемое поведение (раннее связывание)

Есть несколько простых способов привязать текущее значение i к замыканию сразу при создании функции.

1) Использовать аргумент по умолчанию у лямбды

python

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

2) Генератор (list comprehension) с аргументом по умолчанию

python

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

python

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

python

Обратите внимание: в этом примере порядок аргументов в mul подобран так, чтобы partial фиксировал сначала i.

Краткие рекомендации и инсайты

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

Итог

Если вы видите похожую неожиданность при создании функций в цикле — вспомните про позднее связывание и примените одно из решений выше, чтобы «запомнить» текущее значение переменной в момент создания функции.

Report Page