Замыкания и лямбды в циклах Python

Замыкания и лямбды в циклах Python

@python_quiz

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

Условие — код для анализа

python

Код создаёт список лямбд внутри генератора списка и затем вызывает каждую из них. На первый взгляд может показаться, что лямбды "запомнят" текущее значение i при их создании — разберёмся, так ли это.

Что выводит программа

При выполнении этого кода в Python будет напечатано:

text

Почему все три значения одинаковые? Дальше подробное объяснение.

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

  • Лямбда здесь создаётся как замыкание, которое обращается к переменной i.
  • В Python замыкания захватывают переменные по ссылке (late binding), то есть замыкание хранит ссылку на саму переменную i, а не на её значение в момент создания функции.
  • После завершения цикла переменная i имеет последнее значение из range(3), то есть 2.
  • При последующем вызове каждой лямбды они все читают текущее значение i — и получается [2, 2, 2].

Проще: все функции смотрят на одну и ту же переменную i, и к моменту вызова её значение равно 2.

Анализ вариантов ответа (кратко)

  • Вариант, где все элементы равны 2 — соответствует поведению из-за позднего связывания замыканий.
  • Варианты с [0, 1, 2] или [0, 0, 0] предполагают, что лямбды захватили значения на момент создания — это не так по умолчанию.
  • Вариант с NameError неверен: переменная i существует и доступна в момент вызова лямбд (она живёт в окружающей области видимости).

Как получить ожидаемое «значение при создании» — способы фиксации значения

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

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

python

Аргументы по умолчанию вычисляются в момент создания функции и хранят конкретные значения.

2) Создать фабрику функций:

python

Здесь каждая вызванная make создаёт новую локальную переменную i, которая фиксируется в замыкании.

3) Использовать functools.partial (иногда полезно, если нужна более сложная конфигурация):

python

partial фиксирует значение аргумента при создании объекта-обёртки.

Небольшие замечания

  • Это поведение однотипно для Python 3 (и для Python 2 — в том числе; отличие не в механизме замыкания, а в том, что в Python 2 range возвращал список, но сам цикл всё равно задавал i).
  • Частая ошибка у новичков — ожидать «копирование» значения при создании функции в цикле. Полезно помнить правило: замыкание хранит имя переменной, а не её снимок.

Вывод

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

Report Page