free variables /cell objects question

Steven D'Aprano steve at REMOVE.THIS.cybersource.com.au
Thu Jan 25 15:51:23 CET 2007


On Thu, 25 Jan 2007 04:29:35 -0800, Paul Rubin wrote:

> "gangesmaster" <tomerfiliba at gmail.com> writes:
>> what i see as a bug is this code not working as expected:
>> 
>> >>> def make_foos(names):
>> ...     funcs = []
>> ...     for n in names:
>> ...             def foo():
>> ...                     print "my name is", n
>> ...             funcs.append(foo)
>> ...     return funcs
> 
> But it does work as expected, if your expectations are based on what
> closures actually do.
> 
>> i have to create yet another closure, make_foo, so that the name
>> is correctly bound to the object, rather than the frame's slot:
> 
> The Python idiom is:
> 
>    def make_foos(names):
>        funcs = []
>        for n in names:
>                def foo(n=n):
>                        print "my name is", n
>                funcs.append(foo)
>        return funcs
> 
> The n=n in the "def foo" creates the internal binding that you need.

Hmmm... I thought that the introduction of nested scopes removed the need
for that idiom. Its an ugly idiom, the less I see it the happier I am.

And I worry that it will bite you on the backside if your "n=n" is a
mutable value.

My solution is, don't try to have one function do too much. Making a list
of foos should be a separate operation from making a single foo:

>>> def makefoo(name):
...     def foo():
...             return "my name is " + name
...     return foo
...
>>> makefoo("fred")()
'my name is fred'
>>> def makefoos(names):
...     foos = []
...     for name in names:
...             foos.append(makefoo(name))
...     return foos
...
>>> L = makefoos(["fred", "wilma"])
>>> L[0]()
'my name is fred'
>>> L[1]()
'my name is wilma'


That makes it easier to do unit testing too: you can test your makefoo
function independently of your makefoos function, if that's important.

If you absolutely have to have everything in one function:

>>> def makefoos(names):
...     def makefoo(name):
...             def foo():
...                     return "my name is " + name
...             return foo
...     L = []
...     for name in names:
...             L.append(makefoo(name))
...     return L
...
>>> L = makefoos(["betty", "barney"])
>>> L[0]()
'my name is betty'
>>> L[1]()
'my name is barney'


Best of all, now I don't have to argue as to which binding behaviour is
more correct for closures!!! *wink*


-- 
Steven.




More information about the Python-list mailing list