如何将类属性传递给方法装饰器?

3

我正在尝试创建一个类,用于进行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

我如何将参数传递给此方法装饰器?我尝试将它们作为类属性,但这也不起作用。

类属性还是实例属性?为什么__init__要设置类属性,为什么你要尝试将实例属性传递给装饰器? - user2357112
我该如何将值传递给我的方法装饰器?另外,我已经修复了类属性的问题,那只是我尝试的东西。 - flybonzai
你不需要装饰器。把kwargs保存为实例属性,并在需要的时候从循环内部访问它们,以便在make_request中使用。 - chepner
retry 方法是 https://pypi.python.org/pypi/retrying 的一部分,我认为它需要作为装饰器来调用。 - flybonzai
4个回答

4

一些注释/问题:

  1. @retry 装饰器将应用于 make_request 方法 在类创建时,而 retry_kwargs 仅在 类的实例 创建时可用,因此前者必须在后者之前。

在这种情况下, 前者不能依赖于后者中可用的信息, ... 只要您使用装饰器语法...

  1. The decorator syntax

     @decorator
         def xxx(...):
         ...
    

    is just syntax sugar for

     def xxx(...):
         ...
     xxx = decorate(xxx)
    
这意味着,Python非常灵活,您可以通过类似以下的操作来强制执行:
    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

无法确定这个装饰器是否会对self参数产生影响。
你会有多个APIRequester实例吗?如果是这样,注意每次创建新实例时方法都将重新装饰:这样做是否合理?(我怀疑不行。)但请参见下面的编辑...
如果您没有多个实例,则可能不需要依赖在单例构造时可用的信息。
以上是一些Python通用原则。我怀疑您真的想要在这种情况下强制执行此问题。在我看来,你试图以一种它不设计的方式使用装饰器。
编辑:instancemethods
如果将构造函数中执行装饰的行替换为
self.make_request = retry(**kwargs)(self.make_request)

这样每个实例将得到其自己的函数修饰版本。这应该避免了对同一函数进行重新修饰时出现的任何问题。可能仍会有self阻碍的问题。在这种情况下,您可以从定义中删除self参数,并用staticmethod来包装它:

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

或者更好的做法是,使用装饰器语法在定义 make_request 时应用 staticmethod,这也是 Guido 原本想要的方式。

这样做的话,它甚至有可能会正常工作!:-)


现在已经没有装饰器了,而且每个要被装饰的方法都应该在构造函数中有那个丑陋的结构。完全违背了初衷。 - Roman Susi
1
@RomanSusi,有一个装饰器,只是不再使用语法糖了。我在解释Python的原则;我并没有声称它很漂亮,或者是一个好主意,或者很可能按照问题中特定的装饰器正常工作。 - jacg
是的,您的回答具有很高的教育价值,但装饰器语法不再使用。 - Roman Susi

1

当然,在调用装饰器时,self是可用的。请参见如何装饰类内的方法?的答案,我在这里的回答基于该答案:

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()

也就是说,原始的装饰器被一个新的、配置感知的装饰器包装起来。无需改变构造函数,语法仍然简单。

1

装饰器只是一种语法糖,用于 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

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

0

重试装饰器不支持类方法,因为类的实例会被隐式地传递给函数。 请装饰普通函数。 如果您想将函数包装成类,请装饰静态方法。


注意:当你说“类方法”时,人们可能会误解为Python中的classmethod,这不是你想要表达的意思。 - jacg
对于其他人,重复上面的评论。这个答案确实适用于classmethod(因为类对象会自动传递),但是答案中的“类方法”指的是Python中的常规实例方法,因为self被传递,所以也不支持。 - ely

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接