If I make two lists of functions:
def makeFun(i):
return lambda: i
a = [makeFun(i) for i in range(10)]
b = [lambda: i for i in range(10)]
why do lists a and b not behave in the save way?
For example:
>>> a[2]()
2
>>> b[2]()
9
If I make two lists of functions:
def makeFun(i):
return lambda: i
a = [makeFun(i) for i in range(10)]
b = [lambda: i for i in range(10)]
why do lists a and b not behave in the save way?
For example:
>>> a[2]()
2
>>> b[2]()
9
As others have stated, scoping is the problem. Note that you can solve this by adding an extra argument to the lambda expression and assigning it a default value:
>> def makeFun(i): return lambda: i
...
>>> a = [makeFun(i) for i in range(10)]
>>> b = [lambda: i for i in range(10)]
>>> c = [lambda i=i: i for i in range(10)] # <-- Observe the use of i=i
>>> a[2](), b[2](), c[2]()
(2, 9, 2)
The result is that i is now explicitly placed in a scope confined to the lambda expression.
Technically, the lambda expression is closed over the i that's visible in the global scope, which is last set to 9. It's the same i being referred to in all 10 lambdas. For example,
i = 13
print b[3]()
In the makeFun function, the lambda closes on the i that's defined when the function is invoked. Those are ten different is.
To add some clarity (at least in my mind)
def makeFun(i): return lambda: i
a = [makeFun(i) for i in range(10)]
b = [lambda: i for i in range(10)]
a uses makeFun(i) which is a function with an argument.
b uses lambda: i which is a function without arguments. The i it uses is very different from the previous
To make a and b equal we can make both functions to use no arguments:
def makeFun(): return lambda: i
a = [makeFun() for i in range(10)]
b = [lambda: i for i in range(10)]
Now both functions use the global i
>>> a[2]()
9
>>> b[2]()
9
>>> i=13
>>> a[2]()
13
>>> b[2]()
13
Or (more useful) make both use one argument:
def makeFun(x): return lambda: x
a = [makeFun(i) for i in range(10)]
b = [lambda x=i: x for i in range(10)]
I deliberately changed i with x where the variable is local. Now:
>>> a[2]()
2
>>> b[2]()
2
Success !
Nice catch. The lambda in the list comprehension is seeing the same local i every time.
You can rewrite it as:
a = []
for i in range(10):
a.append(makefun(i))
b = []
for i in range(10):
b.append(lambda: i)
with the same result.
b[2]() also returns 9.for loops would make it easier to see where i comes from.