关于python:编写一个decorator来为类的所有方法应用另一个带有参数的decorator

Write a decorator to apply another decorator with arguments for all methods of a class

来自这篇文章。这个被接受的答案很好地适用于不带任何争论的装饰师。我正试图扩展这个解决方案,使它为应用修饰器接受参数。

详细地说,我有进行外部API调用的函数。因为这些调用经常失败,所以我将retry decorator从这个库应用到所有函数。为了避免在所有函数中反复使用@retry(...)行,我决定将它们集中在一个类中。我创建了retryClass,并将所有函数作为classmethod放在类中。现在,我正在寻找一种方法来为类的所有方法应用retry修饰器,这样我就可以继续在类中添加新方法,它将自动为新方法应用retry修饰器。

注意:retry修饰符接受参数。

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

这是我的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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

这将引发以下错误:

1
2
3
4
5
6
7
8
9
10
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)

但是,注释类修饰器运行时没有错误。我有什么东西不见了吗?


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

1
2
3
4
5
6
class RetryClass(object):

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

您的课程需要相当于:

1
2
3
4
5
6
class RetryClass(object):

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


classmethodstaticmethod必须是方法的最后一个修饰符,因为它们返回描述符而不是函数。(更难装饰)。您可以检测一个方法是否已经是classmethodstaticmethod了,然后您的decorate函数将如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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