[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