Dynamic methods and lambda functions

Brian Allen Vanderburg II BrianVanderburg2 at aim.com
Fri Jan 23 12:51:03 EST 2009


unineuro at gmail.com wrote:
> class Person:
>     def __init__(self):
>         for prop in props:
>             setattr(self, "__" + prop[0], prop[1])
>             setattr(Person, "Get" + prop[0], lambda self: getattr
> (self, "__" + prop[0]))
>
>   
I've had a similar problem here and here is best how I can explain it.  
The prop in the lambda function is a closure by 'name' to the prop in 
the containing name space (__init__), so when the lambda function 
executes, it looks for the name 'prop' in this name space and uses it's 
value.  After the 'for prop in props' loop is complete, 'prop' is left 
referring to the last item in props, so each lambda function would use 
it (mary)

One solution is to not use lambda and avoid closures by using default 
arguments:

for prop in props:
    def Getter(self=self, prop=prop):
       return getattr(self, '__' + prop[0])
    setattr(self, '__' + prop[0], prop[1])
    setattr(self, 'Get' + prop[0], Getter)

I have several problems with this though:

1. I don't think this will invoke Pythons name mangling mechanism.  The 
property will be '__name' and not '__Person_name'.

2. If you make a copy of the class, including attributes, the Getter 
will operate on the old class not new:

Person a
b = copy.copy(a)

setattr(a, '__name', bob)
setattr(b, '__name', sarah)

b.Getname() -> bob

In order to make it work, the class must support updating the Getter 
when it is copied to have a new self value.

import copy

class MethodCaller:
    def __init__(self, obj, method, name):
        self.obj = obj
        self.method = method
        self.name = name

        setattr(obj, name, self)

    def __call__(self, *args, **kwargs):
        return self.method(self.obj, *args, **kwargs)

    def copy(self, newobj):
        return MethodCaller(newobj, self.method, self.name)


props = ( ('name', 'mary'), ('age', 21), ('gender', 'female') )

class Person:
    def __init__(self):
        self._methods = []

        for prop in props:
            (name, value) = prop
        
            def getter(self, name=name):
                return getattr(self, '_' + name)

            setattr(self, '_' + name, value)
            self._methods.append(MethodCaller(self, getter, 'Get' + name))

    def copy(self,copymethods=True):
        c = copy.copy(self)
        if copymethods:
            c._methods = []
            for i in self._methods:
                c._methods.append(i.copy(c))
        return c


# Example without copying methods
p = Person()
q = p.copy(False)

p._name = 'sarah'
q._name = 'michelle'

print p.Getname()
print p.Getage()
print p.Getgender()

print q.Getname() # Still prints 'sarah', because getter still refers to 
'p' instead of 'q'
print q.Getage()
print q.Getgender()

# Example with copying methods
p = Person()
q = p.copy()

p._name = 'sarah'
q._name = 'michelle'

print p.Getname()
print p.Getage()
print p.Getgender()

print q.Getname() # Prints 'michelle'
print q.Getage()
print q.Getgender()




More information about the Python-list mailing list