[Tutor] Bitten by lexical closures

Kent Johnson kent37 at tds.net
Wed May 3 21:00:02 CEST 2006


Igor wrote:
> Hi.
> 
> And I thought I understood python pretty well. Until I got hit by this:
> 
>>>> def f(x):
> ...   print x
> 
>>>> cb = [lambda :f(what) for what in "1234"]
>>>> for c in cb:c()
> 4
> 4
> 4
> 4

You haven't actually created a closure because you don't have any nested 
scopes, you have global scope and the scope of the lambda function. A 
list comprehension doesn't introduce a new scope. Your list comp changes 
the value of the global what. Here is a simpler version of what you are 
doing that is not so mysterious:

In [8]: what = 3

In [9]: def f(x):
    ...:     print x
    ...:
    ...:

In [10]: def anon():
    ....:     f(what)
    ....:
    ....:

In [11]: anon()
3

In [12]: what = 4

In [13]: anon()
4

Here it is pretty obvious that anon() is referring to the global what, 
and no surprise that changing what changes the value printed by anon().

There is actually a gotcha here and maybe the code you show is a 
simplified version of some real code that trips over it. The problem is 
that the value in a closure is the value that a variable has when the 
scope creating the closure is exited, not the value when the closure is 
created. So even if your list comp is in a function (and thus a true 
closure) you will see the same result:

In [14]: def makefuncs():
    ....:     return [lambda :f(who) for who in "1234"]
    ....:

In [15]: for func in makefuncs(): func()
    ....:
4
4
4
4

Each function sees the value that who had when makefuncs exited. There 
are two ways to work around this. One is to use a default argument to 
the lambda to bind the value, rather than depending on the closure. This 
would work for your version as well:

In [16]: cb = [lambda what=what:f(what) for what in "1234"]

In [17]: for c in cb:c()
    ....:
1
2
3
4

Another way to do it is to make a factory function that creates a single 
function, and call that in the list comp. Then each value is bound in a 
separate closure:

In [18]: def makefunc(what):
    ....:     return lambda : f(what)
    ....:

In [19]: cb = [makefunc(what) for what in "1234"]

In [20]: for c in cb:c()
    ....:
1
2
3
4

Kent



More information about the Tutor mailing list