【问题标题】:Write a decorator to apply another decorator with arguments for all methods of a class编写一个装饰器来为类的所有方法应用另一个带有参数的装饰器
【发布时间】:2017-07-17 09:55:27
【问题描述】:

来自这个post。接受的答案与不带参数的装饰器很好地配合。我正在尝试扩展此解决方案,使其为应用装饰器提供参数。

详细来说,我有进行外部 api 调用的函数。因为这些调用经常失败,所以我将这个 library 中的重试装饰器应用于所有函数。为了避免为所有功能一次又一次地放置@retry(...) 行,我决定将它们集中在一个类中。我创建了 RetryClass 并将所有函数作为 classmethod 放在类中。现在,我正在寻找一种方法来为类的所有方法应用 retry 装饰器,这样我就可以继续在类中添加新方法,它会自动为新方法应用 retry 装饰器.

注意:重试装饰器接受参数。

@retry(wait_random_min=100, wait_random_max=300, stop_max_attempt_number=3)

这是我的代码:

from retrying import retry


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


@for_all_methods(retry(wait_random_min=100, wait_random_max=300, stop_max_attempt_number=3))
class RetryClass(object):

    @classmethod
    def a(cls):
        pass


def test():
    RetryClass.a()
    return

这会引发以下错误:

Traceback (most recent call last):
  File "/Applications/PyCharm.app/Contents/helpers/pydev/pydevd.py", line 1596, in <module>
    globals = debugger.run(setup['file'], None, None, is_module)
  File "/Applications/PyCharm.app/Contents/helpers/pydev/pydevd.py", line 974, in run
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "/Users/gyoho/Datatron/Dev/class-decorator/main.py", line 26, in <module>
    test()
  File "/Users/gyoho/Datatron/Dev/class-decorator/main.py", line 22, in test
    RetryClass.a()
TypeError: unbound method a() must be called with RetryClass instance as first argument (got nothing instead)

但是,注释掉类装饰器运行没有错误。我有什么遗漏吗?

【问题讨论】:

  • 当你想将它应用到所有方法时,你最好使用meta class

标签: python python-2.7 python-decorators


【解决方案1】:

问题是@classmethod 不再是a() 的顶级装饰器。 RetryClass.a 目前按顺序用@classmethod@retry 装饰。 RetryClass 相当于:

class RetryClass(object):

    @retry(wait_random_min=100, wait_random_max=300, stop_max_attempt_number=3)
    @classmethod
    def a(cls):
        pass

你的类需要等同于:

class RetryClass(object):

    @classmethod
    @retry(wait_random_min=100, wait_random_max=300, stop_max_attempt_number=3)
    def a(cls):
        pass

【讨论】:

  • 我想我只需要避免classmethodstaticmethod 然后。实例化这个类并使用它的方法?
【解决方案2】:

classmethodstaticmethod 必须是方法的最后一个装饰器,因为它们返回 descriptors 而不是函数。 (装饰起来比较棘手)。您可以检测一个方法是否已经是classmethodstaticmethod,然后您的decorate 函数看起来有点像这样:

def decorate(cls):
    for attr in cls.__dict__:
        possible_method = getattr(cls, attr)
        if not callable(possible_method):
            continue

        if not hasattr(possible_method, "__self__"):
            raw_function = cls.__dict__[attr].__func__
            decorated_method = decorator(raw_function)
            decorated_method = staticmethod(decorated_method)

        if type(possible_method.__self__) == type:
            raw_function = cls.__dict__[attr].__func__
            decorated_method = decorator(raw_function)
            decorated_method = classmethod(decorated_method)

        elif possible_method.__self__ is None:
            decorated_method = decorator(possible_method)

        setattr(cls, attr, decorated_method)

    return cls

【讨论】:

  • 也许,从类中实例化一个对象并使用它的方法会更容易吗?
  • @gyoho 这可能会更简单,但请记住,staticmethodproperty 仍然存在问题
  • 我删除了@classmethod 并将cls 更改为self,但没有放入@staticmethod。这次运行良好。这样做有什么缺点?
  • 我看到的主要缺点是您可能希望将来使用该功能。此外,根据您的方法,您可能不希望拥有多个实例(例如多个数据库连接)
  • 我在__init__.py 中实例化了一个对象,并根据需要导入该对象,所以它就像一个单例对象。因为该类没有变量,所以我想对于这种特殊情况可以这样做吗?
猜你喜欢
  • 2011-10-05
  • 1970-01-01
  • 2021-04-20
  • 1970-01-01
  • 2023-03-29
  • 2018-08-15
相关资源
最近更新 更多