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 are the lists a
and b
not equal?
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 are the lists a
and b
not equal?
For example:
>>> a[2]()
2
>>> b[2]()
9
Lambdas in python share the variable scope they're created in. In your first case, the scope of the lambda is makeFun's. In your second case, it's the global
i
, which is 9 because it's a leftover from the loop.That's what I understand of it anyway...
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 samei
being referred to in all 10 lambdas. For example,In the
makeFun
function, the lambda closes on thei
that's defined when the function is invoked. Those are ten differenti
s.Nice catch. The lambda in the list comprehension is seeing the same local
i
every time.You can rewrite it as:
with the same result.
One set of functions (a) operates on the argument passed and the other (b) operates on a global variable which is then set to 9. Check the disassembly:
To add some clarity (at least in my mind)
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:
Now both functions use the global i
Or (more useful) make both use one argument:
I deliberately changed i with x where the variable is local. Now:
Success !
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:
The result is that
i
is now explicitly placed in a scope confined to thelambda
expression.