pythonclassmethodscallable

dynamically adding callable to class as instance "method"


I implemented a metaclass that tears down the class attributes for classes created with it and builds methods from the data from those arguments, then attaches those dynamically created methods directly to the class object (the class in question allows for easy definition of web form objects for use in a web testing framework). It has been working just fine, but now I have a need to add a more complex type of method, which, to try to keep things clean, I implemented as a callable class. Unfortunately, when I try to call the callable class on an instance, it is treated as a class attribute instead of an instance method, and when called, only receives its own self. I can see why this happens, but I was hoping someone might have a better solution than the ones I've come up with. Simplified illustration of the problem:

class Foo(object):
    def __init__(self, name, val):
        self.name = name
        self.val = val
        self.__name__ = name + '_foo'
        self.name = name
    # This doesn't work as I'd wish
    def __call__(self, instance):
        return self.name + str(self.val + instance.val)

def get_methods(name, foo_val):
    foo = Foo(name, foo_val)
    def bar(self):
        return name + str(self.val + 2)
    bar.__name__ = name + '_bar'
    return foo, bar

class Baz(object):
    def __init__(self, val):
        self.val = val

for method in get_methods('biff', 1):
    setattr(Baz, method.__name__, method)
baz = Baz(10)
# baz.val == 10
# baz.biff_foo() == 'biff11'
# baz.biff_bar() == 'biff12'

I've thought of:

  1. Using a descriptor, but that seems way more complex than is necessary here
  2. Using a closure inside of a factory for foo, but nested closures are ugly and messy replacements for objects most of the time, imo
  3. Wrapping the Foo instance in a method that passes its self down to the Foo instance as instance, basically a decorator, that is what I actually add to Baz, but that seems superfluous and basically just a more complicated way of doing the same thing as (2)

Is there a better way then any of these to try to accomplish what I want, or should I just bite the bullet and use some closure factory type pattern?


Solution

  • One way to do this is to attach the callable objects to the class as unbound methods. The method constructor will work with arbitrary callables (i.e. instances of classes with a __call__() method)—not just functions.

    from types import MethodType
    
    class Foo(object):
        def __init__(self, name, val):
            self.val = val
            self.__name__ = name + '_foo'
            self.name = name
        def __call__(self, instance):
            return self.name + str(self.val + instance.val)
    
    class Baz(object):
        def __init__(self, val):
            self.val = val
    
    Baz.biff = MethodType(Foo("biff", 42), None, Baz)
    
    b = Baz(13)
    print b.biff()
    >>> biff55
    

    In Python 3, there's no such thing as an unbound instance method (classes just have regular functions attached) so you might instead make your Foo class a descriptor that returns a bound instance method by giving it a __get__() method. (Actually, that approach will work in Python 2.x as well, but the above will perform a little better.)

    from types import MethodType
    
    class Foo(object):
        def __init__(self, name, val):
            self.name = name
            self.val = val
            self.__name__ = name + '_foo'
            self.name = name
        def __call__(self, instance):
            return self.name + str(self.val + instance.val)
        def __get__(self, instance, owner):
            return MethodType(self, instance) if instance else self
            # Python 2: MethodType(self, instance, owner)
    
    class Baz(object):
        def __init__(self, val):
            self.val = val
    
    Baz.biff = Foo("biff", 42)
    
    b = Baz(13)
    print b.biff()
    >>> biff55