Search code examples
pythonmethodsmetaclasspython-descriptors

callable as instancemethod?


Let's say we've got a metaclass CallableWrappingMeta which walks the body of a new class, wrapping its methods with a class, InstanceMethodWrapper:

import types

class CallableWrappingMeta(type):
    def __new__(mcls, name, bases, cls_dict):
        for k, v in cls_dict.iteritems():
            if isinstance(v, types.FunctionType):
                cls_dict[k] = InstanceMethodWrapper(v)
        return type.__new__(mcls, name, bases, cls_dict)

class InstanceMethodWrapper(object):
    def __init__(self, method):
        self.method = method
    def __call__(self, *args, **kw):
        print "InstanceMethodWrapper.__call__( %s, *%r, **%r )" % (self, args, kw)
        return self.method(*args, **kw)

class Bar(object):
    __metaclass__ = CallableWrappingMeta
    def __init__(self):
        print 'bar!'

Our dummy wrapper just prints the arguments as they come in. But you'll notice something conspicuous: the method isn't passed the instance-object receiver, because even though InstanceMethodWrapper is callable, it is not treated as a function for the purpose of being converted to an instance method during class creation (after our metaclass is done with it).

A potential solution is to use a decorator instead of a class to wrap the methods -- that function will become an instance method. But in the real world, InstanceMethodWrapper is much more complex: it provides an API and publishes method-call events. A class is more convenient (and more performant, not that this matters much).

I also tried some dead-ends. Subclassing types.MethodType and types.UnboundMethodType didn't go anywhere. A little introspection, and it appears they decend from type. So I tried using both as a metaclass, but no luck there either. It might be the case that they have special demands as a metaclass, but it seems we're in undocumented territory at this point.

Any ideas?


Solution

  • Just enrich you InstanceMethodWrapper class with a __get__ (which can perfectly well just return self) -- that is, make that class into a descriptor type, so that its instances are descriptor objects. See http://users.rcn.com/python/download/Descriptor.htm for background and details.

    BTW, if you're on Python 2.6 or better, consider using a class-decorator instead of that metaclass -- we added class decorators exactly because so many metaclasses were being used just for such decoration purposes, and decorators are really much simpler to use.