tags:

views:

146

answers:

3

Given this snippet of code:

funcs = []
for x in range(3):
    funcs.append(lambda: x)
print [f() for f in funcs]

I would expect it to print [0, 1, 2], but instead it prints [2, 2, 2]. Is there something fundamental I'm missing about how lambdas work with scope?

+4  A: 

x is bound to the module-level x (which is left over from the for loop).

A little clearer:

funcs = []

for x in range(10):
    funcs.append(lambda: x)

x = 'Foo'

print [f() for f in funcs]

# Prints ['Foo', 'Foo', 'Foo', 'Foo', 'Foo', 'Foo', 'Foo', 'Foo', 'Foo', 'Foo']
Jon-Eric
+6  A: 

This is a frequent question in Python. Basically the scoping is such that when f() is called, it will use the current value of x, not the value of x at the time the lambda is formed. There is a standard workaround:

funcs = []
for x in range(10):
funcs.append(lambda x=x: x)
print [f() for f in funcs]

The use of lambda x = x retrieves and saves the current value of x.

Kathy Van Stone
+2  A: 

You know the answer: yes. ;) Take comfort, however, as this is a very common discovery for budding pythonistas. When you define a function or lambda that references variables not "created" inside that function, it creates a closure over the variables. The effect is that you get the value of the variable when calling the function, not the value at definition time. (You were expecting the latter.)

There are a few ways to deal with this. First is binding extra variables:

funcs = []
for x in range(10):
    funcs.append(lambda x=x: x)
print [f() for f in funcs]
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

The second way is a little more formal:

from functools import partial
funcs = []
for x in range(10):
    funcs.append(partial(lambda x: x, x))
print [f() for f in funcs]
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Shane Holloway