← Все вопросы

Почему генератор списка внутри функции ведёт себя неожиданно?

Задан 2 месяца назад8 просмотров1 ответ
0

Странное поведение генератора списково def make_multipliers(): return [lambda x: x * i for i in range(3)]

for f in make_multipliers(): print(f(2)) Ожидал увидеть 0, 2, 4, а получил 4, 4, 4. Я знаю про позднее связывание, но как это починить красиво?

1 ответ

0

Это классическая ловушка позднего связывания (late binding), и ты прав, что дело в нём — давай прицельно, почему так и как починить красиво.

Лямбды lambda x: x * i не запоминают значение i в момент создания. Они запоминают саму переменную i — ссылку на неё. К моменту, когда ты их вызываешь, цикл уже закончился, и i во всех трёх лямбдах указывает на одно и то же последнее значение — 2. Отсюда и 4, 4, 4.

Чинится «заморозкой» текущего i в момент создания лямбды. Самый чистый способ — аргумент по умолчанию, он вычисляется сразу:

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

Теперь у каждой лямбды своя копия i, и вывод — 0, 2, 4, как ты и ждал.

Если не хочется «лишнего» параметра — functools.partial:

from functools import partial
def make_multipliers():
    return [partial(lambda i, x: x * i, i) for i in range(3)]

Но i=i читается проще, и это самый ходовой приём. Та же ловушка живёт и в замыканиях с обычным def внутри цикла, не только в лямбдах — принцип один.

Ваш ответ

, чтобы ответить на вопрос.