0

When using lambda expression in for loop and variable i, all lambda expression stored in tmp list are dependent on i.

tmp = []
for i in range(5):
    tmp.append(lambda x: x + i)
print(tmp[0](1)) # Prints 5 should print 1
print(tmp[4](1)) # Prints 5 should print 5

Basically, it boils down to this problem, where lambda expression is dependent on a.

a = 10
foo = lambda x: x + a
print(foo(1)) # Prints 11 should print 11
a = 20
print(foo(1)) # Prints 21 should print 11

Is it possible to use var for initialization but afterwards not be dependent afterwards anymore. Solution for the for loop with lambda expression is needed. Thanks

I tried to boild down the problem and also tried a hack with lambda x: x + eval(str(i)) to paste the value and not the local variable.

disp
  • 1
  • 2
  • Why tmp[0](1) should print 2? – matszwecja Dec 13 '22 at 10:06
  • You are right should print 1 – disp Dec 13 '22 at 10:11
  • 1
    The "principled" approach to this is to create *a factory function*, `def func_maker(i): return lambda x: x + i` then in your loop, `tmp.append(func_maker(i))`. Alternatively, you can use an idiom in Python that takes advantage of the way default values work, i.e., they are evaluated once when the function is defined, so `tmp.append(lambda x, i=i: x + i)`. I personally prefer the former – juanpa.arrivillaga Dec 13 '22 at 10:11
  • Thanks for the idea with factory function. Also was now pointed to https://stackoverflow.com/questions/2295290/what-do-lambda-function-closures-capture by stackoverflow – disp Dec 13 '22 at 10:15

1 Answers1

2

You can use this little trick with default value to enforce capture of variable's value at the time of its creation, rather than it being taken at the moment function is executed.

tmp = []
for i in range(5):
    tmp.append(lambda x, i = i: x + i)
print(tmp[0](1)) # 1
print(tmp[4](1)) # 5

As a general rule, scope of the variable is decided at the moment of definition, and value is looked up then the code is actually being run.

Thus, when you define your lambda, as i assigned to in lambda, Python decides its scope to be nonlocal/global. Then, when we actually execute the function the value of i is looked up to be 5 in the chosen scope.

With default value, the variable becomes local, so its value get's "saved" as well.

matszwecja
  • 6,357
  • 2
  • 10
  • 17
  • "enforce the closure on the variable" So, I know what you mean but the way this is phrased implies the opposite of what is happening. In the case *with the default argument no closure is created* because `i` is no longer a free variable, it is a *parameter*. It is in the case *without a default argument a closure might be created* because `i` is a free variable (in this particular case, a global variable so no closure object is actually created because it is simply resolved with a global name lookup, but only free variables will ever possibly be closed over) – juanpa.arrivillaga Dec 13 '22 at 10:19
  • @juanpa.arrivillaga agreed, I've changed the wording and added a bit of explanation of the mechanism. – matszwecja Dec 13 '22 at 10:28