可调用作为实例方法?
假设我们有一个元类 CallableWrappingMeta
,它会遍历新类的内容,把它的方法用一个类 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!'
我们的这个简单的包裹器只是把传入的参数打印出来。但是你会注意到一个明显的问题:这个方法没有接收到实例对象,因为虽然 InstanceMethodWrapper
是可以调用的,但在类创建的时候,它并不被当作一个函数来处理(在我们的元类处理完之后)。
一个可能的解决办法是使用装饰器来包裹这些方法——这样这个函数就会变成实例方法。但在实际情况中,InstanceMethodWrapper
要复杂得多:它提供了一个API,并发布方法调用事件。用类来实现会更方便(而且性能更好,虽然这不是特别重要)。
我也尝试了一些无果的方向。对 types.MethodType
和 types.UnboundMethodType
进行子类化并没有成功。经过一些反思,我发现它们都是从 type
继承而来的。所以我尝试把它们作为元类使用,但也没有成功。可能它们作为元类有特殊的要求,但现在看来我们进入了没有文档支持的领域。
有没有什么想法?
4 个回答
我猜你是想创建一个元类,这个元类会把类里的每个方法都包裹在一个自定义的函数里。
这是我写的版本,我觉得这样说起来会简单一些。
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")
编辑: 我又说错了。函数上的 __?attr__
属性是只读的,但显然在你赋值时并不总是会抛出 AttributeException
异常?我也不太清楚。又回到原点了!
编辑: 这其实并没有解决问题,因为包装函数不会把属性请求转发给 InstanceMethodWrapper
。当然,我可以在装饰器中直接修改 __?attr__
属性——这也是我现在在做的——但这样看起来不太好。更好的想法非常欢迎。
当然,我立刻意识到,把一个简单的装饰器和我们的类结合起来就能解决这个问题:
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
然后你在元类中把装饰器加到对 InstanceMethodWrapper
的调用上:
cls_dict[k] = methodize(v, InstanceMethodWrapper(v))
好了。虽然有点拐弯抹角,但确实有效。
只需要给你的 InstanceMethodWrapper
类添加一个 __get__
方法(这个方法可以简单地返回 self
),这样就把这个类变成了一个 描述符 类型,这样它的实例就成了描述符对象。想了解更多背景和细节,可以查看这个链接:http://users.rcn.com/python/download/Descriptor.htm。
顺便说一下,如果你使用的是 Python 2.6 或更高版本,可以考虑使用类装饰器,而不是元类。我们之所以添加类装饰器,就是因为很多元类只是为了装饰的目的而使用,而装饰器的使用方式要简单得多。