Problem understanding how closures work

Discussion in 'Python' started by Tom Plunket, Dec 12, 2006.

  1. Tom Plunket

    Tom Plunket Guest

    ....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.ExternalCall(self.name)

    # The first test.
    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

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

    def CreateTest(index):
    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()
    Tom Plunket, Dec 12, 2006
    #1
    1. Advertising

  2. Tom Plunket wrote in news: 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/
    Rob Williscroft, Dec 12, 2006
    #2
    1. Advertising

  3. On 12 dic, 17:23, Tom Plunket <> 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(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, 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
    Gabriel Genellina, Dec 12, 2006
    #3
  4. Tom Plunket

    Tom Plunket Guest

    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!
    Tom Plunket, Dec 12, 2006
    #4
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. alain

    problem with closures

    alain, Dec 6, 2006, in forum: Python
    Replies:
    1
    Views:
    248
    Gerard Brunick
    Dec 7, 2006
  2. Ramashish Baranwal

    Understanding closures

    Ramashish Baranwal, Aug 19, 2007, in forum: Python
    Replies:
    3
    Views:
    240
    James Stroud
    Aug 19, 2007
  3. Louis Steinberg

    problem with lambda / closures

    Louis Steinberg, Nov 30, 2009, in forum: Python
    Replies:
    1
    Views:
    242
    Marco Mariani
    Nov 30, 2009
  4. Terry Reedy

    Re: problem with lambda / closures

    Terry Reedy, Nov 30, 2009, in forum: Python
    Replies:
    2
    Views:
    271
    Terry Reedy
    Dec 1, 2009
  5. Replies:
    2
    Views:
    69
Loading...

Share This Page