Рассмотрите пример на Python: funcs = [lambda x: i*x for i in range(3)]; что выведет funcs[0](2) и почему, как исправить, если требуется захват текущего значения i при создании каждого замыкания
Ответ: funcs[0](2) вернёт 4. Пояснение: в выражении funcs = [lambda x: i*x for i in range(3)] переменная iii имеет значения 0,1,20,1,20,1,2 в ходе итерации, но лямбды захватывают саму переменную, а не её текущее значение (late binding). После завершения цикла iii равно 222, поэтому при вызове любой из фукнций вычисляется 2∗2=42*2=42∗2=4. Исправления (чтобы захватить текущее значение iii при создании замыкания): 1) Через аргумент по умолчанию (самый простой): funcs = [lambda x, i=i: i*x for i in range(3)] Теперь funcs[0](2) даст 000, funcs[1](2) — 222, funcs[2](2) — 444. 2) Через фабрику функций: def make(i): return lambda x: i*x funcs = [make(i) for i in range(3)] 3) Через functools.partial: from functools import partial def mul(i, x): return i*x funcs = [partial(mul, i) for i in range(3)] Везде выше переменная iii фиксируется при создании каждой функции, поэтому замыкания работают как ожидается.
Пояснение: в выражении
funcs = [lambda x: i*x for i in range(3)]
переменная iii имеет значения 0,1,20,1,20,1,2 в ходе итерации, но лямбды захватывают саму переменную, а не её текущее значение (late binding). После завершения цикла iii равно 222, поэтому при вызове любой из фукнций вычисляется 2∗2=42*2=42∗2=4.
Исправления (чтобы захватить текущее значение iii при создании замыкания):
1) Через аргумент по умолчанию (самый простой):
funcs = [lambda x, i=i: i*x for i in range(3)]
Теперь funcs[0](2) даст 000, funcs[1](2) — 222, funcs[2](2) — 444.
2) Через фабрику функций:
def make(i):
return lambda x: i*x
funcs = [make(i) for i in range(3)]
3) Через functools.partial:
from functools import partial
def mul(i, x): return i*x
funcs = [partial(mul, i) for i in range(3)]
Везде выше переменная iii фиксируется при создании каждой функции, поэтому замыкания работают как ожидается.