Что выведет код с замыканием в списке лямбда-функций

49. Что выведет код с замыканием в списке лямбда-функций

Условие задачи:
Что выведет код?

def multipliers():
  return [lambda x : i * x for i in range(4)]
 
 print [[m(2) for m in multipliers()]
Спойлеры к решению
Подсказки
  • В коде используется список lambda-функций.
  • Каждая lambda использует переменную i из внешней области видимости.
  • Значение i не фиксируется в момент создания lambda.
  • Все lambda-функции будут смотреть на одно и то же последнее значение i.
  • После завершения range(4) последнее значение i будет 3.
  • Поэтому каждая функция фактически посчитает 3 * x.
Решение

В исходном виде код записан с синтаксической ошибкой:

print [[m(2) for m in multipliers()]

Корректный вариант для Python 3:

def multipliers():
    return [lambda x: i * x for i in range(4)]


print([m(2) for m in multipliers()])

Результат:

[6, 6, 6, 6]

Почему так происходит:

return [lambda x: i * x for i in range(4)]

Может показаться, что создаются такие функции:

lambda x: 0 * x
lambda x: 1 * x
lambda x: 2 * x
lambda x: 3 * x

Но на самом деле все lambda-функции запоминают не значение i, а ссылку на переменную i.

После завершения цикла последнее значение i равно 3. Поэтому каждая lambda работает так:

lambda x: 3 * x

Когда вызывается:

m(2)

каждая функция возвращает:

3 * 2 = 6

Поэтому итоговый список:

[6, 6, 6, 6]

Чтобы получить ожидаемый результат [0, 2, 4, 6], нужно зафиксировать текущее значение i в аргументе по умолчанию:

def multipliers():
    return [lambda x, i=i: i * x for i in range(4)]


print([m(2) for m in multipliers()])

Результат:

[0, 2, 4, 6]

Здесь i=i сохраняет текущее значение i на каждой итерации, поэтому каждая lambda получает своё собственное значение.