Rüdiger Ranft wrote:
Hi all,

I want to generate some methods in a class using setattr and lambda.
Within each generated function a name parameter to the function is
replaced by a string constant, to keep trail which function was called.
The problem I have is, that the substituted name parameter is not
replaced by the correct name of the function, but by the last name the
for loop has seen.

import unittest

class WidgetDummy:
    '''This class records all calls to methods to an outer list'''

    def __init__( self, name, calls ):
        '''name is the name of the object, which gets included into a
        call record. calls is the list where the calls are appended.'''
        self.name = name
        self.calls = calls
        for fn in ( 'Clear', 'Append', 'foobar' ):
            func = lambda *y,**z: self.__callFn__( fn, y, z )
            setattr( self, fn, func )

    def __callFn__( self, fnName, *args ):
        '''Add the function call to the call list'''
        self.calls.append( ( self.name, fnName, args ) )

class Testcase( unittest.TestCase ):
    def testFoo( self ):
        calls = []
        wd = WidgetDummy( 'foo', calls )
        wd.Append( 23 )
        self.assertEqual( [ ( 'foo', 'Append', ( 23, {} ) ) ], calls )

unittest.main()

This is a common problem on this list. I was possibly the last one to ask what was essentially the same question, though it was on the wxPython list, 3/15/09 - "Using lambda function within event binding"

Anyway, the answer is that when (free) variables are used inside any nested function (a def inside a def, or a lambda), these variables are not copied, they are scoped from the nesting environment. So even though the __init__ may have ended, all these lambda functions still retain a reference to the same 'fn' variable, which is kept alive till all the lambda functions are gone. Lookup closures if you want to understand more, or use dis() on the function.

The simplest solution is a hack, but a common one. Use default variables, which have a lifetime that matches the lambda, but are initialized when the lambda is being created. To do this, you'd add an extra optional argument to the lambda, and use that as the first argument to the call. Unfortunately, although this is what I used, I don't know if you can do it when you're already using the *y syntax. In the line below, I assumed you could change to exactly one non-keyword argument.

    func = lambda y, unlikelydummyname=fn, **z: self.__callFn__( fn, y, **z )


Incidentally, in your example, I believe you needed the *y and **z in the actual parameters to __callFn__(). You had omitted the asterisks. Further, you had no keyword arguments in the formal parameter list. So I'm not sure where you were really headed. But perhaps the above will get you started.


--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to