在python中将参数传递给装饰器

在python中将参数传递给装饰器,python,decorator,python-decorators,Python,Decorator,Python Decorators,我正在使用软件包中的重试功能。我想从函数中传递retry装饰器的参数,但我不确定如何实现 @retry # (wait_exponential_multiplier=x,wait_exponential_max=y) def post(url, json, exponential_multiplier, exponential_max): ... return(abc) 调用post()时,我想传递重试的参数。我知道当编译函数时,生成的函数对象被传递给装饰器,因此我不确定这是否

我正在使用软件包中的重试功能。我想从函数中传递
retry
装饰器的参数,但我不确定如何实现

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

调用
post()
时,我想传递
重试的参数。我知道当编译
函数时,生成的
函数
对象被传递给
装饰器
,因此我不确定这是否可行,或者我是否应该以不同的方式处理它。

一般来说,没有好的方法来实现这一点。您当然可以编写如下代码:

def post(url, json):
    ...
    return(abc)

...
decorated_func = retry(wait_exponential_max=1)(post)
a = decorated_func(url, json)
它会起作用的。但它看起来相当难看,并且会为每次调用构造装饰对象(“常规”装饰器在导入时执行一次)

如果decorator本身不是很复杂,那么您可以以更加用户友好的方式使用此方法:

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)

如果您只想按原样使用库,那么就不能像这样使用装饰器。它的参数从被调用时起就保持不变(除了乱搞可变参数)。相反,您可以在每次调用函数之前调用decorator。这允许您在需要时更改重试参数

例如

但在这一点上,您最好完全跳过装饰程序,使用装饰程序正在使用的内容

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=...)

您必须创建一个新的装饰器,将其自己的参数向下传递给装饰函数,并使用
重试
装饰器转换函数:

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)

这是对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)


限制:上面的代码只接受并传递装饰器的关键字参数,因此您希望
post
函数具有签名
post(url、json、wait\u exponential\u multiplier、exponential\u max)
?啊,是的,现在将对其进行编辑。谢谢
@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)