From: Artur Siekielski on
Hello.
I found this strange behaviour of lambdas, closures and list
comprehensions:

>>> funs = [lambda: x for x in range(5)]
>>> [f() for f in funs]
[4, 4, 4, 4, 4]

Of course I was expecting the list [0, 1, 2, 3, 4] as the result. The
'x' was bound to the final value of 'range(5)' expression for ALL
defined functions. Can you explain this? Is this only counterintuitive
example or an error in CPython?


Regards,
Artur
From: Raymond Hettinger on
On May 6, 9:34 pm, Artur Siekielski <artur.siekiel...(a)gmail.com>
wrote:
> Hello.
> I found this strange behaviour of lambdas, closures and list
> comprehensions:
>
> >>> funs = [lambda: x for x in range(5)]
> >>> [f() for f in funs]
>
> [4, 4, 4, 4, 4]
>
> Of course I was expecting the list [0, 1, 2, 3, 4] as the result. The
> 'x' was bound to the final value of 'range(5)' expression for ALL
> defined functions. Can you explain this? Is this only counterintuitive
> example or an error in CPython?

Try binding the value of x for each of the inner functions:

>>> funs = [lambda x=x: x for x in range(5)]
>>> [f() for f in funs]
[0, 1, 2, 3, 4]

Otherwise, the 'x' is just a global value and the lambdas look it up
at when the function is invoked. Really, not surprising at all:

>>> x = 10
>>> def f():
.... return x
....
>>> x = 20
>>> f()
20


Raymond

From: Emile van Sebille on
On 5/6/2010 12:34 PM Artur Siekielski said...
> Hello.
> I found this strange behaviour of lambdas, closures and list
> comprehensions:
>
>>>> funs = [lambda: x for x in range(5)]

funs is now a list of lambda functions that return 'x' (whatever it
currently is from whereever it's accessible when invoked)



>>> [f() for f,x in zip(funs,range(5))]
[0, 1, 2, 3, 4]
>>> del x
>>> [f() for f in funs]
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 1, in <lambda>
NameError: global name 'x' is not defined
>>>

Emile

From: Benjamin Peterson on
Artur Siekielski <artur.siekielski <at> gmail.com> writes:
>
> Of course I was expecting the list [0, 1, 2, 3, 4] as the result. The
> 'x' was bound to the final value of 'range(5)' expression for ALL
> defined functions. Can you explain this? Is this only counterintuitive
> example or an error in CPython?

The former. Closures are rebound in a loop.




From: Terry Reedy on
On 5/6/2010 3:34 PM, Artur Siekielski wrote:
> Hello.
> I found this strange behaviour of lambdas, closures and list
> comprehensions:
>
>>>> funs = [lambda: x for x in range(5)]
>>>> [f() for f in funs]
> [4, 4, 4, 4, 4]

You succumbed to lambda hypnosis, a common malady ;-).
The above will not work in 3.x, which does not leak comprehension
iteration variables. It is equivalent to

funs = [lambda: x for y in range(5)]
del y # only for 2.x. y is already gone in 3.x
x = 4
[f() for f in funs]

Now, I am sure, you would expect what you got.

and nearly equivalent to

def f(): return x
x=8
funs = [f for x in range(5)]
[f() for f in funs]

# [8,8,8,8,8] in 3.x

Ditto

Terry Jan Reedy