Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to generate a list of different lambda functions with list comprehension?

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.

like image 783
Lyman Hurd Avatar asked Dec 24 '22 17:12

Lyman Hurd


1 Answers

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)]
like image 108
unutbu Avatar answered Dec 28 '22 23:12

unutbu