This question is distilled from the original application involving callback functions for Tkinter buttons. This is one line that illustrates the behavior.
lambdas = [lambda: i for i in range(3)]
if you then try invoking the lambda functions generated:
lambdas[0]()
, lambdas[1]()
and lambdas[2]()
all return 2.
The desired behavior was to have lambdas[0]()
return 0, lambdas[1]()
return 1, lambdas[2])()
return 2.
I see that the index variable is interpreted by reference. The question is how to rephrase to have it treated by value.
Use a parameter with default value to bind the current value of i
to a local variable. When the lambda
gets called without an argument, the local variable i
is assigned the default value:
In [110]: lambdas = [lambda i=i: i for i in range(3)]
In [111]: for lam in lambdas:
.....: print(lam())
.....:
0
1
2
When i
is not a local variable, Python looks up its value in the enclosing scope. The value that is found is the last value i
attained in the for-loop of the list comprehension. That is why, without the parameter with default value, each lambda
returns 2 since the for-loop has completed by the time the lambdas get called.
Another common approach to solving this problem is to use a closure -- a function that can refer to environments that are no longer active such as the local namespace of an outer function, even after that function has returned.
def make_func(i):
return lambda: i
lambdas = [make_func(i) for i in range(3)]
for lam in lambdas:
print(lam())
prints
0
1
2
This works because when lam()
gets called, since the i
in the lambda
function's body is not a local variable, Python looks for the value of i
in
the enclosing scope of the function make_func
. Its local namespace is still
accessible to the closure, lam
, even though make_func
has already
completed. The value in that local namespace is the value which was passed to
make_func
, which is, happily, the desired value of i
.
As already mentioned by mkrieger1,
another way to create a new function with some argument values already supplied
is to use functools.partial
:
lambdas = [functools.partial(lambda x: x, i) for i in range(3)]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With