Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using python lambda in generator expressions, or for-loops [duplicate]

I am writing some scientific python code. At some point in the code I need to accept a list of callable objects (existing_functions), and then produce another list of callable objects (changed_functions) by injecting a change-of-variables in the original objects' __call__ methods. To me, the following seems like a nice abstraction.

import numpy as np
changed_functions = [lambda x: f(np.exp(x)) for f in existing_functions]

I worry about misusing lambda here. So I ran some simple tests involving lambda in loops and generator expressions. Those simple tests produced confounding results, which seem to involve variable-scope or pass-by-reference issues which I cannot decipher.

The code snippet below tries four different methods to define a list of anonymous functions using lambda. Only the first method (with a list literal) works as I would hope.

# attempt 1: use a list literal
funcs = [lambda: 1, lambda: 2]
print(funcs[0]())  # 1
print(funcs[1]())  # 2
# attempt 2: much closer to my actual use-case.
gunks = [lambda: i for i in [1,2]]
print(gunks[0]())  # 2, but I expect 1.
print(gunks[1]())  # 2, as expected.
# attempt 3: an ugly version of my use case, which I thought might work.
hunks = [(lambda: i,)[0] for i in [1,2]]
print(hunks[0]())  # 2, but I expect 1.
print(hunks[1]())  # 2, as expected.
# attempt 4: another ugly version of my use case. "WE NEED MORE LAMBDA."
yunks = [(lambda: lambda: i)() for i in [1,2]]
print(yunks[0]())  # 2, but I expect 1.
print(yunks[1]())  # 2, as expected.

I am hoping that someone can explain to me why the last three methods in the snippet above all produce such a different result, compared to a list-literal.

like image 753
Riley Murray Avatar asked Jan 24 '26 22:01

Riley Murray


1 Answers

You bind this with the i variable, but since you later change i, it will look for a variable with that name in the scope, and thus pick the last value you assigned to that i.

You can resolve this by adding an extra parameter to your lambda function:

changed_functions = [lambda i=i: i for i in [1,2]]

Or if you do not want that, you can use currying [wiki] here:

changed_functions = [(lambda j: lambda: j)(i) for i in [1, 2]]

or we can use an explicit function:

def function_maker(j):
    return lambda: j

changed_functions = [function_maker(i) for i in [1, 2]]

We here thus create a lambda-expression that takes a variable i and returns a lambda expression that returns that i. We then bind the i with our value for i, and thus construct an i in a more local scope.

for both it then returns:

>>> changed_functions[0]()
1
>>> changed_functions[1]()
2
like image 194
Willem Van Onsem Avatar answered Jan 27 '26 12:01

Willem Van Onsem



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!