python-3.x 如何将类属性传递给方法装饰器?

x0fgdtte  于 2022-12-05  发布在  Python
关注(0)|答案(4)|浏览(164)

我正在尝试创建一个类,它将发出api请求,根据传递给retrying.retry装饰器的配置选项进行重试,并以正确的方式为每个作业处理不同的错误代码。
下面是我的代码:

from retrying import retry

class APIRequester:
    def __init__(self, url, **kwargs):
        self.url = url
        self.retry_kwargs = kwargs

    @retry(**self.retry_kwargs) # Obviously doesn't know what self is
    def make_request(self):
        pass

我如何将参数传递给这个方法装饰器呢?我试着将它们变成一个类属性,但也没有成功。

2o7dmzc5

2o7dmzc51#

A couple of notes/questions:

  1. The @retry decorator will be applied to the make_request method at the time the class is created, while retry_kwargs will only become available when an instance of the class is created, and thus the former must precede the latter.
    In which case, the former cannot depend on information that becomes available in the latter, ... as long as you use the decorator syntax ...
  2. The decorator syntax
@decorator
     def xxx(...):
     ...

is just syntax sugar for

def xxx(...):
     ...
 xxx = decorate(xxx)

which means that, along with the fact that Python is very dynamic, you could force the issue by doing something like

class APIRequester:
        def __init__(self, url, **kwargs):
            self.url = url
            self.retry_kwargs = kwargs
            APIRequester.make_request = retry(**kwargs)(APIRequester.make_request)

        def make_request(self):
            pass

Whether this particular decorator chokes on the self parameter or not, I cannot tell you.
Will you have more than one instance of APIRequester ? If so, note that the method will be re-decorated each time a new instance is created: can this work sensibly? (I doubt it.) But see the edit below ...
If you do not have more that one instance, then you probably don't need to rely on information that becomes availale at the singleton's construction time.
The above were some general Python principles. I doubt that you really want to force the issue in this case. It seems to me that you are trying to use the decorator in a way that it was not designed to be used.
Edit: instancemethods
If you replace the line that does the decorating in the constructor with

self.make_request = retry(**kwargs)(self.make_request)

then each instance will get its own decorated version of the function. This should avoid any problems with re-decoration of the same function. There may will still be problems with self getting in the way. In that case, you could remove the self parameter from the definition and wrap it with staticmethod :

self.make_request = retry(**kwargs)(staticmethod(self.make_request))

Or better still, use decorator syntax to apply staticmethod to make_request at the place where you define it, the way Guido inteded it.
Like this, it even stands a chance of working! :-)

ds97pgxw

ds97pgxw2#

Decorator只是func=decorator(func)的一个语法糖,你可以自己赋值:

class APIRequester:
    def __init__(self, url, **kwargs):
        self.url = url
        self.make_request = retry(**kwargs)(self.make_request)

    def make_request(self):
        pass

这将在内部用函数替换方法(描述符),但它将按预期工作。

3ks5zfa0

3ks5zfa03#

当然,self在调用时可以在装饰器中使用,参见对How to decorate a method inside a class?的回答,我的回答基于此:

def my_retry(fn):
    from functools import wraps
    @wraps(fn)
    def wrapped(self):
        print(self.retry_kwargs)
        for i in range(self.retry_kwargs["times"]):
            # you have total control
            fn(self)
            # around your method. Can even call it multiple times,
            # call with original retry: 
        retry(**self.retry_kwargs)(fn)(self)
        # check exceptions, return some value (here None), etc
        # 
    return wrapped

class APIRequester(object):
    def __init__(self, url, **kwargs):
        self.url = url
        self.retry_kwargs = kwargs

    @my_retry
    def make_request(self):
        print("method")

a = APIRequester('http://something', times=3)
a.make_request()

也就是说,原来的装饰器被一个新的,配置感知的装饰器 Package 。不需要改变构造器,语法保持简单。

lnlaulya

lnlaulya4#

重试装饰器不支持类方法,因为类的示例已隐式传递给函数。请装饰普通函数。如果要将函数 Package 到类中,请装饰静态方法。

相关问题