如何为类的所有方法装饰而不重复输入?

101 投票
5 回答
58655 浏览
提问于 2025-04-16 19:19

假设我的类里面有很多方法,我想在每个方法上都加上一个装饰器。以后如果我再添加新的方法,我也希望这个装饰器能自动应用,但我不想每次都在方法前面写 @mydecorator

我在想,查看 __call__ 这个方法是不是解决这个问题的好办法呢?


我想展示一种方法,这个方法和我的问题类似,供以后有同样疑问的人参考,使用了一种叫做 mixin 的方式,正如评论中提到的。

class WrapinMixin(object):
    def __call__(self, hey, you, *args):
        print 'entering', hey, you, repr(args)
        try:
            ret = getattr(self, hey)(you, *args)
            return ret
        except:
            ret = str(e)
            raise
        finally:
            print 'leaving', hey, repr(ret)
    

然后你可以在另一个地方

class Wrapmymethodsaround(WrapinMixin): 
    def __call__:
         return super(Wrapmymethodsaround, self).__call__(hey, you, *args)

编辑注:这个例子似乎解决的问题和提问的内容不太一样。

5 个回答

18

我不是想重新提起旧事,但我真的很喜欢delnan的回答,不过觉得有一点点不够完整。

def for_all_methods(exclude, decorator):
    def decorate(cls):
        for attr in cls.__dict__:
            if callable(getattr(cls, attr)) and attr not in exclude:
                setattr(cls, attr, decorator(getattr(cls, attr)))
        return cls
    return decorate

补充:修正了缩进问题

所以你可以指定一些方法、属性或者其他东西,不想让它们被装饰。

40

虽然我不太喜欢用一些神奇的方法来解决问题,特别是当有更直接的方法可以用的时候,但你可以考虑使用元类来实现这个功能。

def myDecorator(fn):
    fn.foo = 'bar'
    return fn

class myMetaClass(type):
    def __new__(cls, name, bases, local):
        for attr in local:
            value = local[attr]
            if callable(value):
                local[attr] = myDecorator(value)
        return type.__new__(cls, name, bases, local)

class myClass(object):
    __metaclass__ = myMetaClass
    def baz(self):
        print self.baz.foo

这样做的效果就像是每个可以调用的东西在 myClass 里都被加上了 myDecorator 的装饰。

>>> quux = myClass()
>>> quux.baz()
bar
101

给这个类加上一个装饰器函数,这个函数会遍历类里的属性,并把可调用的属性进行装饰。如果你的类里面有一些变量恰好是可调用的,这样做可能就不太合适了。此外,这个方法还会装饰嵌套的类(感谢Sven Marnach指出这一点),但总体来说,这是一种相对简单干净的解决方案。下面是一个示例实现(注意,这个方法不会排除特殊方法,比如__init__等,这可能是你想要的,也可能不是):

def for_all_methods(decorator):
    def decorate(cls):
        for attr in cls.__dict__: # there's propably a better way to do this
            if callable(getattr(cls, attr)):
                setattr(cls, attr, decorator(getattr(cls, attr)))
        return cls
    return decorate

使用方法如下:

@for_all_methods(mydecorator)
class C(object):
    def m1(self): pass
    def m2(self, x): pass
    ...

撰写回答