Получить доступ

Почему лямбды, созданные в цикле, возвращают одно и то же значение?

MiddleФункции
Ещё спрашивают так:
  • Что такое позднее связывание в замыканиях Python?
Коротко

Замыкание захватывает переменную, а не её значение: значение читается в момент вызова. К моменту вызова цикл закончился, и все лямбды видят последнее значение i. Исправление — зафиксировать значение: аргумент по умолчанию (lambda i=i: i) или functools.partial.

  • [lambda: i for i in range(3)] → все возвращают 2
  • Значения по умолчанию вычисляются при определении функции — здесь это и нужно
  • Та же проблема с колбэками в цикле (кнопки GUI, обработчики)

А ещё Отличие от Java: там лямбда захватывает значение effectively final переменной

Так подсказку видит пользователь sotto на живом собеседовании — за пару секунд, пока вы думаете вслух.

Подробно

funcs = [lambda: i for i in range(3)]
print([f() for f in funcs])        # [2, 2, 2]

funcs = [lambda i=i: i for i in range(3)]
print([f() for f in funcs])        # [0, 1, 2]

from functools import partial
funcs = [partial(lambda x: x, i) for i in range(3)]

Почему. Замыкание хранит ссылку на ячейку (cell) переменной внешней области. Все лямбды ссылаются на одну ячейку i; при вызове каждая читает её текущее значение.

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

Что хотят услышать

  • Захват переменной, а не значения.
  • Два способа исправить.

Связанные вопросы

Закрытая бета

Подсказки на собеседовании — в реальном времени

sotto слышит вопрос интервьюера и показывает короткий ответ и план, как в блоке «Коротко». Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем