Замыкания и параметры по умолчанию в Python

Замыкания и параметры по умолчанию в Python

@python_quiz

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

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

Исходный код

python

Что тут происходит на каждом шаге:

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

Почему работает именно так (пояснение)

В Python замыкания захватывают переменные по ссылке, не по значению. Если бы мы написали лямбду без параметра по умолчанию:

python

то все три лямбды использовали бы одну и ту же внешнюю переменную i. После завершения цикла i равно 2, поэтому все вызовы вернули бы 12 — результат позднего связывания (late binding).

Параметр по умолчанию (i=i) создаёт локальную переменную в момент определения функции и присваивает ей текущее значение внешней i. Таким образом, значение фиксируется «по месту и времени» создания функции.

Демонстрация и сравнение вариантов

Оригинальный код (фиксирует i):

python

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

python

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

python

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

python

Анализ вариантов ответов (какие выводы соответствуют каким случаям)

  • Вывод вида [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 по умолчанию связывают имя, а не значение — это частая ловушка для начинающих.

Если хотите, могу показать ещё примеры с более сложными циклами (вложенные циклы, объекты и т.п.) или перевести это поведение на асинхронные сценарии — скажите, что интересует.

Report Page