Problem understanding how closures work

Rob Williscroft rtw at freenet.co.uk
Tue Dec 12 16:36:05 EST 2006


Tom Plunket wrote in news:cm3un2hec05uqndvumko3je03k59k00eqp at 4ax.com in 
comp.lang.python:

> ...at least, I think that I'm having a problem understanding the way
> closures work.
> 
> I'm trying to define a function for an object which will take certain
> objects from the parent scope at the time that function is defined.
> For some reason, if I do this function definition in a loop, the
> locals given by that function (is this a closure?) are changed each
> iteration of the loop, whereas if the function definition is isn't
> looped over, I get the behavior I desire.  Can anyone provide any
> insight for me?

> Test 1 doesn't work the way I would expect:
> Test 4 says, "Test 0"

> Test 4 says, "Test 4"
> 

> def CreateTests1(count):
>      tests = []
>      for i in xrange(count):
>           name = 'Test %d' % i
>           t = Test(name)
>           tests.append(t)
>           
>           def ExCall(text):
>                print '%s says, "%s"' % (name, text)
>                
>           t.ExternalCall = ExCall
>           
>      return tests

"name" in the above code is bound to a an entry in "CreateTests1"'s 
locals, and ExCall has a (hidden) reference to that locals, so 
by the time ExCall is finally called the value associated 
with "name" has been replaced by (count - 1).

The solution (as always) is to add another level of indirection:

def create_tests( count ):
  def make( arg ):
   def ExCall( text ):
     print arg, text
   return ExCall

  tests = []

  for i in range( count ):
    name = i
    t = Test( name )
    t.ExternalCall = make( name )

In the above, every call to make() creates a new frame (a new set
of locals) and binds the value of the passed in "name" to the 
name "arg" in this new frame, it will be this value that is
eventually printed.

There is a trick with default arguments that lets you do
what you want with a bit less faffing about:

>>> r = []
>>> for i in range(10):
	def f( i = i ):
		print i
	r.append( f )
	
>>> for i in r:
	i()

In this example the value of "i" is bound to the default argument
for the function "f" every time the def f() statments are executed.

Rob.
-- 
http://www.victim-prime.dsl.pipex.com/



More information about the Python-list mailing list