Late Binding

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

print([m(2) for m in multipliers()])
# expect [0, 2, 4, 6]
# actual [6, 6, 6, 6]

This is caused by late binding, a fundamental mechanism that allows the variables in the closure to be looked up when called, not when it's defined. When multipliers() returns, i=3, hence, the returned anoymous functions becomes 3*i.

To resolve this unwanted behaviour, we could explicitly bind i at each iteration of the loop.

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

Alternatively, we could use functools.partial to freeze some portion of the function's arguments, and geneartes a new and simplier calllable.

import functools
def multipliers():
    return [functools.partial(lambda i, x: x * i, i) for i in range(4)]

print([m(2) for m in multipliers()]) 
# resulting [0, 2, 4, 6]