Problem understanding how closures work

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Tom Plunket

    #1

    Problem understanding how closures work

    ....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?

    thanks,
    -tom!

    First, the output:

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

    ....but test 2 does?
    Test 0 says, "Test 0"
    Test 1 says, "Test 1"
    Test 2 says, "Test 2"
    Test 3 says, "Test 3"
    Test 4 says, "Test 4"

    Next, the program:

    class Test:
    def __init__(self, name):
    self.name = name

    def DoCall(self):
    self.ExternalCa ll(self.name)

    # The first test.
    def CreateTests1(co unt):
    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

    # The second test.
    def CreateTests2(co unt):
    tests = []
    for i in xrange(count):
    t = CreateTest(i)
    tests.append(t)
    return tests

    def CreateTest(inde x):
    name = 'Test %d' % index
    t = Test(name)

    def ExCall(text):
    print '%s says, "%s"' % (name, text)

    t.ExternalCall = ExCall
    return t

    print 'Test 1 doesn\'t work the way I would expect:'
    for t in CreateTests1(5) :
    t.DoCall()

    print '\n...but test 2 does?'
    for t in CreateTests2(5) :
    t.DoCall()
  • Rob Williscroft

    #2
    Re: Problem understanding how closures work

    Tom Plunket wrote in news:cm3un2hec0 5uqndvumko3je03 k59k00eqp@4ax.c om in
    comp.lang.pytho n:
    ...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(co unt):
    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.
    --

    Comment

    • Gabriel Genellina

      #3
      Re: Problem understanding how closures work

      On 12 dic, 17:23, Tom Plunket <t...@fancy.org wrote:
      ...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.
      def CreateTests1(co unt):
      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, inside ExCall, is a free variable. Python builds a closure
      including the string whose name is "name" in the enclosing scope. Not
      the *value* which happens to have at this momment. When you execute
      ExCall, the reference to name yields its last, current, value.
      If you want "the value at the moment the function is created" you can
      use a default argument:

      def ExCall(text, name=name): ...

      Your second test works because you don't modify "name" between the
      original definition and its execution.

      --
      Gabriel Genellina

      Comment

      • Tom Plunket

        #4
        Re: Problem understanding how closures work

        Rob Williscroft wrote:
        "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).
        Ah, I got it. Thanks. Thanks too to Gabriel.


        -tom!

        Comment

        Working...