0

I was expecting Python's lambda to keep reference to value of local variables. So when I wrote:

>>> ls = [
...     lambda: x*x
...     for x in range(3)
... ]
>>> [f() for f in ls]
[4, 4, 4]

I expected the result to be: [0,1,4] where each lambda would remember a different value of x used to create it. But as you can see above, the result is [4,4,4] as if they all have been eval against the last value of x. This is unintuitive as x does not even exists in the context the lambdas are evaluated in.

>>> x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined

Just to make sure, I create a local scope to change value of x after the last lambda creation to see if the evaluation use the last value of x and it sure did.

>>> def get_ms():
...     ms = []
...     for x in range(3):
...         ms.append(lambda: x*x)
...     print([f() for f in ms])
...     x = 3
...     return ms
...
>>> ms = get_ms()
[4, 4, 4]
>>> [f() for f in ms]
[9, 9, 9]

NOTE: It appears inner function also works the same way.

First question: Is my above analysis of the situation correct? (Lambda remember references to local variable used to create it rather than value of the variable, and this variable somehow persist outside its scope)

Second question: What is behaviour supposed to be used for? Or is just an undefined part of specification?

The easiest way I can think of to get the behaviour I originally wanted is:

>>> import functools
>>> def square(x):
...     return x*x
...
>>> ls_ = [
...     functools.partial(square, x)
...     for x in range(3)
... ]
>>> [f() for f in ls_]
[0, 1, 4]

Final question: Is there a better way than using functools.partial as seen above?

Apiwat Chantawibul
  • 1,271
  • 1
  • 10
  • 20
  • 1
    It works the same as with other functions: it looks up the value of the variable named `x` *when called*. The function body isn’t evaluated until call time. – deceze Feb 12 '22 at 07:24
  • 1
    BTW, the usual way of getting your desired behaviour is `[lambda x=x: x*x for x in range(3)]` – ForceBru Feb 12 '22 at 07:25
  • The way *I* prefer is to make an explicit function factory, `def square_maker(x): return lambda x: x` and then in your loop, `ls = [square_maker(x) for x in range(3)]` – juanpa.arrivillaga Feb 12 '22 at 08:11
  • @ForceBru That is nice, thank you. – Apiwat Chantawibul Feb 13 '22 at 00:58
  • And thanks to the one who mark this question as duplicate. I'll leave the question up for extra search coverage unless there is a suggestion to just delete it. – Apiwat Chantawibul Feb 13 '22 at 01:02

0 Answers0