可调用作为实例方法?

3 投票
4 回答
762 浏览
提问于 2025-04-15 11:22

假设我们有一个元类 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.MethodTypetypes.UnboundMethodType 进行子类化并没有成功。经过一些反思,我发现它们都是从 type 继承而来的。所以我尝试把它们作为元类使用,但也没有成功。可能它们作为元类有特殊的要求,但现在看来我们进入了没有文档支持的领域。

有没有什么想法?

4 个回答

0

我猜你是想创建一个元类,这个元类会把类里的每个方法都包裹在一个自定义的函数里。

这是我写的版本,我觉得这样说起来会简单一些。

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")
0

编辑: 我又说错了。函数上的 __?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))

好了。虽然有点拐弯抹角,但确实有效。

4

只需要给你的 InstanceMethodWrapper 类添加一个 __get__ 方法(这个方法可以简单地返回 self),这样就把这个类变成了一个 描述符 类型,这样它的实例就成了描述符对象。想了解更多背景和细节,可以查看这个链接:http://users.rcn.com/python/download/Descriptor.htm

顺便说一下,如果你使用的是 Python 2.6 或更高版本,可以考虑使用类装饰器,而不是元类。我们之所以添加类装饰器,就是因为很多元类只是为了装饰的目的而使用,而装饰器的使用方式要简单得多。

撰写回答