在Python中向装饰器传递参数

3

我正在使用来自retrying包的重试函数。我希望从一个函数中传递retry装饰器的参数,但我不确定如何实现。

@retry # (wait_exponential_multiplier=x,wait_exponential_max=y)
def post(url, json, exponential_multiplier, exponential_max):
    ...
    return(abc)

我希望在调用 post() 时传递 retry 的参数。我知道在编译function时,生成的function对象被传递给decorator,因此我不确定是否可能实现这一点 - 或者我应该采用不同的方法。


所以你想要的是让 post 函数具有签名 post(url, json, wait_exponential_multiplier, exponential_max) 吗? - Edward Minnix
啊,好的,我现在会编辑它。谢谢。 - user2715898
4个回答

2
一般来说,没有一种很好的方法来实现这个。你当然可以编写类似于以下代码的代码:
def post(url, json):
    ...
    return(abc)

...
decorated_func = retry(wait_exponential_max=1)(post)
a = decorated_func(url, json)

它可以正常工作。但是它看起来相当丑陋,并且每次调用都会构造装饰对象(“常规”装饰器在导入时执行一次)。

如果装饰器本身不是非常复杂-您可以以更加用户友好的方式使用此方法:

def _post(url, json):
    return(abc)

def post(url, json, wait_exponential_max=None, **kwargs):
    return retry(wait_exponential_max=wait_exponential_max, **kwargs)(_post)(url, json)

2
如果您只是想使用库本身,那么您实际上无法像这样使用装饰器。它的参数是在调用时固定的(除了对可变参数进行操作)。相反,您可以在每次调用函数之前始终调用装饰器。这使您可以根据需要更改重试参数。
例如:
def post(url, json):
    ...

rety(post, wait_exponential_multiplier=...)(url=..., json=...)

但此时,您可以完全跳过装饰器,并使用装饰器正在使用的内容。

from retrying import Retrying

def post(url, json):
    ...

Retrying(wait_exponential_multiplier=...).call(post, url=..., json=...)

这两种方式都可以让您保持post函数的纯洁性,并将其与重试的概念分离开来(使在不需要重试行为时更容易调用post)。

您还可以编写一个便捷函数作为包装器,为程序填充默认值。例如:

def retrier(wait_exponential_multiplier=2, **kwargs):
    return Retrying(wait_exponential_multiplier=wait_exponential_multiplier, **kwargs)

retrier(wait_exponential_max=10).call(post, url=..., json=...)
retrier(wait_exponential_multiplier=3, wait_exponential_max=10).call(post, url=..., json=...)

0

您需要创建一个新的装饰器,将其自己的参数传递给被装饰的函数,并使用retry装饰器转换该函数:

def retry_that_pass_down_arguments(**decorator_arguments):
    def internal_decorator(f):
        def decorated_function(*args, **kwargs):
            # Add the decorator key-word arguments to key-word arguments of the decorated function
            kwargs.update(decorator_arguments) 
            return retry(**decorator_arguments)(f)(*args, **kwargs) 
        return decorated_function
    return internal_decorator

然后你可以这样做:

@retry_that_pass_down_arguments(wait_exponential_multiplier=x, wait_exponential_max=y)
def post(url, json, exponential_multiplier=None, exponential_max=None):
    ...
    return(abc)

0
这是对Jundiaius的答案的补充,以展示您甚至可以使用inspect模块来正确处理修饰函数的签名。
def deco_and_pass(deco, **kwparams):
    """Decorates a function with a decorator and parameter.
The parameters are passed to the decorator and forwarded to the function
The function must be prepared to receive those parameters, but they will
be removed from the signature of the decorated function."""
    def outer(f):
        sig = inspect.signature(f)       # remove parameters from the function signature
        params = collections.OrderedDict(sig.parameters)
        for k in kwparams:
            del params[k]
        def inner(*args, **kwargs):      # define the decorated function
            kwargs.update(kwparams)      # add the parameters
            # and call the function through the parameterized decorator
            return deco(**kwparams)(f)(*args, **kwargs)
        inner.__signature__ = inspect.signature(f).replace(
            parameters = params.values())
        inner.__doc__ = f.__doc__        # update doc and signature
        return inner
    return outer

使用示例:

@deco_and_pass(retry,wait_exponential_multiplier=x,wait_exponential_max=y)
def post(url, json, exponential_multiplier, exponential_max):
    ...
    return(abc)
...
post(url, json)

被装饰函数的签名仅为def post(url, json)

限制:上述代码仅接受和传递装饰器的关键字参数


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