Дан Python-код: funcs = [lambda x: i * x for i in range(5)]; print([f(2) for f in funcs]) — объясните полученный результат, почему он отличается от ожидаемого [0,2,4,6,8] и предложите несколько исправлений
Результат будет [8,8,8,8,8][8,8,8,8,8][8,8,8,8,8], а не ожидаемые [0,2,4,6,8][0,2,4,6,8][0,2,4,6,8]. Почему: в выражении-замыкании (lambda) переменная iii захватывается по ссылке, не по значению. Все лямбды ссылаются на одну и ту же переменную iii, её значение определяется в момент вызова функций. После завершения цикла iii равно 4\,44, поэтому f(2)=4⋅2=f(2)=4\cdot 2=f(2)=4⋅2= 8\,88 для каждой функции. Исправления (несколько вариантов): 1) Фиксировать значение iii как параметр по умолчанию funcs = [lambda x, i=i: i * x for i in range(5)] print([f(2) for f in funcs]) # -> [0,2,4,6,8][0,2,4,6,8][0,2,4,6,8] 2) Заводить замыкание через фабрику функций def make(i): return lambda x: i * x funcs = [make(i) for i in range(5)] print([f(2) for f in funcs]) # -> [0,2,4,6,8][0,2,4,6,8][0,2,4,6,8] 3) Использовать functools.partial или operator.mul from functools import partial from operator import mul funcs = [partial(mul, i) for i in range(5)] print([f(2) for f in funcs]) # -> [0,2,4,6,8][0,2,4,6,8][0,2,4,6,8] 4) Немного менее красиво — немедленно вызывать вложенный лямбда для фиксации значения funcs = [(lambda v: (lambda x: v * x))(i) for i in range(5)] print([f(2) for f in funcs]) # -> [0,2,4,6,8][0,2,4,6,8][0,2,4,6,8] Кратко: проблема — позднее привязывание (late binding) переменной цикла; решение — захват текущего значения (например, через параметр по умолчанию или фабрику).
Почему: в выражении-замыкании (lambda) переменная iii захватывается по ссылке, не по значению. Все лямбды ссылаются на одну и ту же переменную iii, её значение определяется в момент вызова функций. После завершения цикла iii равно 4\,44, поэтому f(2)=4⋅2=f(2)=4\cdot 2=f(2)=4⋅2= 8\,88 для каждой функции.
Исправления (несколько вариантов):
1) Фиксировать значение iii как параметр по умолчанию
funcs = [lambda x, i=i: i * x for i in range(5)]
print([f(2) for f in funcs]) # -> [0,2,4,6,8][0,2,4,6,8][0,2,4,6,8]
2) Заводить замыкание через фабрику функций
def make(i):
return lambda x: i * x
funcs = [make(i) for i in range(5)]
print([f(2) for f in funcs]) # -> [0,2,4,6,8][0,2,4,6,8][0,2,4,6,8]
3) Использовать functools.partial или operator.mul
from functools import partial
from operator import mul
funcs = [partial(mul, i) for i in range(5)]
print([f(2) for f in funcs]) # -> [0,2,4,6,8][0,2,4,6,8][0,2,4,6,8]
4) Немного менее красиво — немедленно вызывать вложенный лямбда для фиксации значения
funcs = [(lambda v: (lambda x: v * x))(i) for i in range(5)]
print([f(2) for f in funcs]) # -> [0,2,4,6,8][0,2,4,6,8][0,2,4,6,8]
Кратко: проблема — позднее привязывание (late binding) переменной цикла; решение — захват текущего значения (например, через параметр по умолчанию или фабрику).