avasal
avasal

Reputation: 14854

Getting confused with lambda and list comprehension

Read a question on stack overflow sometime back with the following syntax

In [1]: [lambda: x for x in range(5)][0]()
Out[1]: 4
In [2]: [lambda: x for x in range(5)][2]()
Out[2]: 4

But i am having a hard time to understand why exactly the output of this comes as 4, my understanding is it always gives the last value of the list as output,

In [4]: [lambda: x for x in [1,5,7,3]][0]()
Out[4]: 3

but still not convinced how does this syntax ends up with the last value.

Would be very glad if i can get a proper explanation for this syntax

Upvotes: 15

Views: 1026

Answers (5)

newacct
newacct

Reputation: 122419

As others have said, you've run into the common loop-closure problem; i.e. the problem that a closure captures variables by reference. In this case you are capturing a variable whose value changes over time (as with all loop variables), so it has a different value when you run it than when you created it.

To accomplish what you want, you need to capture the value of the variable at the time the lambda is created. sblom suggested the solution of wrapping it in an immediately-executed function:

[(lambda(i): lambda: i)(x) for x in range(5)][2]()

Another solution that you will often see in Python is using default arguments, which will also evaluate the value at creation time:

[lambda i=x: i for x in range(5)][2]()

(it's usually written lambda x=x: x, but I renamed the variable for clarity)

Upvotes: 2

Michael J. Barber
Michael J. Barber

Reputation: 25032

This isn't really about either list comprehensions or lambdas. It's about the scoping rules in Python. Let's rewrite the list comprehension into an equivalent loop:

funcs = []
for x in range(5):
    def f(): return x
    funcs.append(f)
funcs[0]() # returns 4

Here, we can see that we successively construct functions and store them in a list. When one of these functions is called, all that happens is the value of x is looked up and returned. But x is a variable whose value changes, so the final value of 4 is what is always returned. You could even change the value of x after the loop, e.g.,

x = 32 
funcs[2]() # returns 32

To get the behavior you expected, Python would need to scope the for contents as a block; it doesn't. Usually, this isn't a problem, and is easy enough to work around.

Upvotes: 15

shashaDenovo
shashaDenovo

Reputation: 1638

Let me break the code for my understanding

In [39]: [lambda: x for x in [1,5,7,3]]
Out[39]: 
[<function <lambda> at 0x2cd1320>,
 <function <lambda> at 0x2cd12a8>,
 <function <lambda> at 0x2cd10c8>,
 <function <lambda> at 0x2cd1050>]

above gives the list of functions

In [40]: [lambda: x for x in [1,5,7,3]][1]
Out[40]: <function <lambda> at 0x2cd1488>

The index 1 gives 1 function from the list of functions.

Now this function will apply on x, which has always the last value of list. Thats y always gives the last value as result.

like in below code.

In [41]: [lambda: 2][0]()
Out[41]: 2


In [42]: alist = [1,5,7,3,4,5,6,7]

x for x in [1,5,7,3] is equivalent to below function f(x). and
lambda: x for x in [1,5,7,3] is equivalent to lambda: 3

In [43]: def f(x):
   ....:     for x in alist:
   ....:         pass
   ....:     return x
In [44]: print f(alist)
7

Upvotes: 3

sblom
sblom

Reputation: 27343

This will fix it:

[(lambda(i): lambda: i)(x) for x in range(5)][2]()

The problem is that you're not capturing the value of x on each iteration of the list comprehension, you're capturing the variable each time through.

Upvotes: 1

Ignacio Vazquez-Abrams
Ignacio Vazquez-Abrams

Reputation: 798436

For a LC with n iterations, x is assigned elements 0 through n-1 of the source sequence. At the final iteration, x is assigned the last element. The point to note is that it's always the same x, and calling the function at the end returns whatever x held last.

Upvotes: 4

Related Questions