callable as instancemethod?
Question
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.
OTHER TIPS
Edit: I lie yet again. The __?attr__
attributes on functions are readonly, but apparently do not always throw an AttributeException
exception when you assign? I dunno. Back to square one!
Edit: This doesn't actually solve the problem, as the wrapping function won't proxy attribute requests to the InstanceMethodWrapper
. I could, of course, duck-punch the __?attr__
attributes in the decorator--and it is what I'm doing now--but that's ugly. Better ideas are very welcome.
Of course, I immediately realized that combining a simple decorator with our classes will do the trick:
def methodize(method, callable):
"Circumvents the fact that callables are not converted to instance methods."
@wraps(method)
def wrapper(*args, **kw):
return wrapper._callable(*args, **kw)
wrapper._callable = callable
return wrapper
Then you add the decorator to the call to InstanceMethodWrapper
in the metaclass:
cls_dict[k] = methodize(v, InstanceMethodWrapper(v))
Poof. A little oblique, but it works.
I'm guessing you are trying to make a metaclass that wraps every method in the class with a custom function.
Here is my version which I think is a little bit less oblique.
import types
class CallableWrappingMeta(type):
def __new__(mcls, name, bases, cls_dict):
instance = type.__new__(mcls, name, bases, cls_dict)
for k in dir(instance):
v = getattr(instance, k)
if isinstance(v, types.MethodType):
setattr(instance, k, instanceMethodWrapper(v))
return instance
def instanceMethodWrapper(function):
def customfunc(*args, **kw):
print "instanceMethodWrapper(*%r, **%r )" % (args, kw)
return function(*args, **kw)
return customfunc
class Bar(object):
__metaclass__ = CallableWrappingMeta
def method(self, a, b):
print a,b
a = Bar()
a.method("foo","bar")
I think you need to be more specific about your problem. The original question talks about wrapping a function, but your subsequent answer seems to talk about preserving function attributes, which seems to be a new factor. If you spelled out your design goals more clearly, it might be easier to answer your question.