Reputation: 14854
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
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
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
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
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
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