Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Weird behavior: Lambda inside list comprehension

In python 2.6:

[x() for x in [lambda: m for m in [1,2,3]]]

results in:

[3, 3, 3]

I would expect the output to be [1, 2, 3]. I get the exact same problem even with a non list comprehension approach. And even after I copy m into a different variable.

What am I missing?

like image 535
GeneralBecos Avatar asked Sep 09 '11 23:09

GeneralBecos


People also ask

Can we use lambda in list comprehension?

List comprehension is used to create a list. Lambda function process is the same as other functions and returns the value of the list. List comprehension is more human-readable than the lambda function. User can easily understand where the list comprehension is used .

What is the difference between lambda and list comprehension?

The difference between Lambda and List Comprehension. List Comprehension is used to create lists, Lambda is function that can process like other functions and thus return values or lists.

Is list comprehension faster than lambda?

Actually, list comprehension is much clearer and faster than filter+lambda, but you can use whichever you find easier. The first thing is the function call overhead: as soon as you use a Python function (whether created by def or lambda) it is likely that the filter will be slower than the list comprehension.

What is lambda in list?

Python Lambda Functions are anonymous function means that the function is without a name. As we already know that the def keyword is used to define a normal function in Python. Similarly, the lambda keyword is used to define an anonymous function in Python.


4 Answers

To make the lambdas remember the value of m, you could use an argument with a default value:

[x() for x in [lambda m=m: m for m in [1,2,3]]]
# [1, 2, 3]

This works because default values are set once, at definition time. Each lambda now uses its own default value of m instead of looking for m's value in an outer scope at lambda execution time.

like image 53
unutbu Avatar answered Sep 23 '22 09:09

unutbu


The effect you’re encountering is called closures, when you define a function that references non-local variables, the function retains a reference to the variable, rather than getting its own copy. To illustrate, I’ll expand your code into an equivalent version without comprehensions or lambdas.

inner_list = []
for m in [1, 2, 3]:
    def Lambda():
         return m
    inner_list.append(Lambda)

So, at this point, inner_list has three functions in it, and each function, when called, will return the value of m. But the salient point is that they all see the very same m, even though m is changing, they never look at it until called much later.

outer_list = []
for x in inner_list:
    outer_list.append(x())

In particular, since the inner list is constructed completely before the outer list starts getting built, m has already reached its last value of 3, and all three functions see that same value.

like image 38
SingleNegationElimination Avatar answered Sep 22 '22 09:09

SingleNegationElimination


Long story short, you don't want to do this. More specifically, what you're encountering is an order of operations problem. You're creating three separate lambda's that all return m, but none of them are called immediately. Then, when you get to the outer list comprehension and they're all called the residual value of m is 3, the last value of the inner list comprehension.

-- For comments --

>>> [lambda: m for m in range(3)]
[<function <lambda> at 0x021EA230>, <function <lambda> at 0x021EA1F0>, <function <lambda> at 0x021EA270>]

Those are three separate lambdas.

And, as further evidence:

>>> [id(m) for m in [lambda: m for m in range(3)]]
[35563248, 35563184, 35563312]

Again, three separate IDs.

like image 43
g.d.d.c Avatar answered Sep 21 '22 09:09

g.d.d.c


Look at the __closure__ of the functions. All 3 point to the same cell object, which keeps a reference to m from the outer scope:

>>> print(*[x.__closure__[0] for x in [lambda: m for m in [1,2,3]]], sep='\n')
<cell at 0x00D17610: int object at 0x1E2139A8>
<cell at 0x00D17610: int object at 0x1E2139A8>
<cell at 0x00D17610: int object at 0x1E2139A8>

If you don't want your functions to take m as a keyword argument, as per unubtu's answer, you could instead use an additional lambda to evaluate m at each iteration:

>>> [x() for x in [(lambda x: lambda: x)(m) for m in [1,2,3]]]
[1, 2, 3]
like image 34
Eryk Sun Avatar answered Sep 22 '22 09:09

Eryk Sun