如何装饰一个类的所有函数而不为每个方法一遍又一遍地键入它? [复制]

Posted

技术标签:

【中文标题】如何装饰一个类的所有函数而不为每个方法一遍又一遍地键入它? [复制]【英文标题】:How to decorate all functions of a class without typing it over and over for each method? [duplicate] 【发布时间】:2011-09-12 13:26:29 【问题描述】:

假设我的类有很多方法,我想在每个方法上应用我的装饰器,稍后当我添加新方法时,我希望应用相同的装饰器,但我不想在上面写 @mydecorator一直在声明方法?

如果我调查__call__,这是正确的方法吗?

重要提示:下面的示例似乎解决了与原始问题不同的问题。

编辑:我想以这种方式展示,对于以后发现这个问题的任何人来说,这是一个类似的解决方案,使用 cmets 中提到的 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)

【问题讨论】:

你能提供一个“稍后”添加方法的例子吗? @TokenMacGuy:我认为他是在谈论更改源代码,而不是关于以编程方式添加方法。 @delnan,是的,我就是这个意思。 嗯,这是一种解脱,其中许多技术只适用于前者,但不适用于后者。 我不明白这是如何工作的,因为 call 仅在对象作为函数调用时使用,例如。 ob = MyClass(); ob(),这里似乎不是这样。我错过了什么吗? 【参考方案1】:

以上答案都不适合我,因为我还想装饰继承的方法,这不是通过使用__dict__ 来完成的,而且我不想让元类过于复杂。最后,我对 Python 2 的解决方案感到满意,因为我只是迫切需要添加一些分析代码来测量类的所有函数使用的时间。

import inspect
def for_all_methods(decorator):
    def decorate(cls):
        for name, fn in inspect.getmembers(cls, inspect.ismethod):
            setattr(cls, name, decorator(fn))
        return cls
    return decorate

来源(略有不同的解决方案):https://***.com/a/3467879/1243926 在那里你还可以看到如何为 Python 3 更改它。

正如 cmets 对其他答案的建议,请考虑改用 inspect.getmembers(cls, inspect.isroutine)。如果您找到了适用于 Python 2 和 Python 3 并修饰继承方法的适当解决方案,并且仍然可以在 7 行中完成,请编辑。

【讨论】:

【参考方案2】:

不是为了让死者复活,但我真的很喜欢德尔南的回答,但发现它有点缺乏。

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

编辑:修复缩进

所以你可以指定方法//属性//你不想装饰的东西

【讨论】:

其实你真的可以发疯。您可以包含而不是排除(但不能同时包含,因为这没有意义......)和其他东西。但这在这里是非常强大的魔法。 总是欢迎改进,随着时间的推移,有人可能会发现它很有用 这就是我所希望的!感谢您的信任投票:P 我喜欢这个。也许排除应该默认为无。 :D @davegallant "maybe exclude should default to None though" - 这不是一个好主意 - TypeError: NoneType object is not iterable ...【参考方案3】:

你可以生成一个元类。这不会修饰继承的方法。

def decorating_meta(decorator):
    class DecoratingMetaclass(type):
        def __new__(self, class_name, bases, namespace):
            for key, value in list(namespace.items()):
                if callable(value):
                    namespace[key] = decorator(value)

            return type.__new__(self, class_name, bases, namespace)

    return DecoratingMetaclass

这将生成一个元类,用指定的函数装饰所有方法。您可以通过执行以下操作在 Python 2 或 3 中使用它

def doubling_decorator(f):
    def decorated(*a, **kw):
        return f(*a, **kw) * 2
    return decorated

class Foo(dict):
    __metaclass__ = decorating_meta(doubling_decorator)

    def lookup(self, key):
        return self[key]

d = Foo()
d["bar"] = 5
print(d.lookup("bar")) # prints 10

【讨论】:

【参考方案4】:

使用遍历类属性并装饰可调用对象的函数来装饰类。如果您有可能碰巧可调用的类变量,并且还会装饰嵌套类(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
    ...

在 Python 3.0 和 3.1 中,callable 不存在。它在 Python 2.x 中一直存在,并且在 Python 3.2 中作为 isinstance(x, collections.Callable) 的包装器重新存在,因此您可以在这些版本中使用它(或使用它定义您自己的 callable 替换)。

【讨论】:

请注意,这也会装饰嵌套类。 (我的实现也有同样的问题。) 为什么不使用 inspect.getmembers(cls, inspect.ismethod) 而不是 __dict__callable() ?当然,在这种情况下,静态方法是不可能的。 在 Python 3 中,inspect.getmembers(cls, inspect.ismethod) 不起作用,因为 inspect.ismethod 为未绑定的方法返回 False。在 Python 2 中,inspect.ismethod 为未绑定的方法返回 True,但 inspect.isfunction 返回 False。也许最好写inspect.getmembers(cls, inspect.isroutine),因为这对两者都有效。 我即将做类似的事情。这在2014年仍然是一个好方法吗?您能否更新您的答案以使用检查而不是 __dict__ 东西? 使用cls.__dict__ 不会修饰继承的方法,但inspect 方法会。【参考方案5】:

虽然我不喜欢使用显式方法时使用神奇的方法,但您可能可以为此使用元类。

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

【讨论】:

这也很有趣,谢谢! 你能评论一下 mixin 方法吗? 请注意:callable(<class>)True。这种行为可能需要也可能不需要,具体取决于您的用例。 我也在研究这种方法,它很棒。这是一个讨论元类的好参考:blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses 这个参考中提到的一个好点是,这种方法也处理子类,而我相信类装饰器不会。这对我来说很重要! 我不认为这很神奇,而是元类有一个糟糕的公关部门。只要合适,就应该使用它们,就像这里的情况一样。很好的解决方案!

以上是关于如何装饰一个类的所有函数而不为每个方法一遍又一遍地键入它? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

如何防止 Firebase 一遍又一遍地创建同一个用户?

如何阻止innerHtml表一遍又一遍地重复自己?

为啥在反应的“useState”钩子中一遍又一遍地设置初始状态

一遍又一遍地运行相同的junit测试的简单方法?

为啥这不会一遍又一遍地返回一个新值?爪哇

一遍又一遍地从 AppSettings 中读取整数