如何将参数传递给装饰器,在那里进行处理,然后转发给装饰函数?

How can I pass arguments to decorator, process there, and forward to decorated function?

我最近决定学习 Python 装饰器,并关注 this howto。特别是 "passing arguments to decorators" 部分下的示例。我想要做的是 (1) 装饰函数应该接受一些参数 (decarg),(2) 处理它们,以及 (3) 使用参数调用装饰函数 (funarg)。我看到了如何为装饰器提供参数,但我还没有看到任何示例,其中装饰函数的参数仅在装饰器中可用。当我调用装饰函数时,我完全不确定应该使用什么参数,因为它的参数将在装饰函数中计算。这是一个基于提到的 howto 的示例:

def dec(decarg):
    def _dec(fun):
        funarg = decarg + 7
        def _fun(funarg):
            return fun(funarg)
        return _fun
    return _dec

def main(mainarg):
    decarg = mainarg + 2
    @dec(decarg)
    def fun1(funarg):
        return funarg + 3
    return fun1(decarg)

main(1)

这个 returns 6,当我期望 13 时。我希望参数在 main() 中增加 2,在 _dec() 中增加 7,这应该将这个变量传递给修饰函数,将其加 3。

之后,我阅读了 this and this howtos,并使用他们的方法创建了一个与我想象的一样有效的示例:

class dec(object):

    def __init__(self, fun):
        self.fun = fun

    def __call__(self, decarg):
        funarg = decarg + 7
        return self.fun(funarg)

def main(mainarg):
    decarg = mainarg + 2
    @dec
    def fun1(funarg):
        return funarg + 3
    return fun1(decarg)

main(1)

所以现在我的问题是:如何对第一种表示法做同样的事情,其中​​装饰器不是 class,而是一个函数?您能否澄清一下,它是如何工作的,哪些参数以及何时传递给 __init__() 以及什么传递给装饰器的 __call__() 方法?

在考虑装饰器如何转换函数以及函数本身是 Python 中的对象之后,原因就很直接了。

让我们从后者开始。

函数是对象:

当我们考虑函数名后两对括号的含义时,这会立即出现。考虑这个简单的例子 (Python 3):

def func(x):
    def func2(y):
        return x + y + 1
    return func2

result = func(5)(10)
print(result)  # 15

这里 "func" return 是一个函数对象 "func2" 因此你可以使用:

func(5)(10)

您可以将此视为先调用

func(5)

并将“(10)”应用于作为函数的结果对象!所以你有:

func2(10)

既然"x"和"y"都定义好了,"func2"就可以return最后的值变成"result"。

记住,这一切都是可能的,因为函数本身就是对象,因为 "func" return 是一个函数对象

func2

不是它的结果(它不是自己调用函数)

func2()

简而言之,这意味着对于包装函数,第二组参数用于内部函数(如果包装器 return 是内部函数对象)。

装饰器:

在您的示例中,"main" 在最后一行用

调用 "fun1"
return fun1(decarg)

由于装饰器

@dec(decarg)

实际上你可以把"fun1"想成:

fun1 = dec(decarg)(fun1)

因此,"main" 中的最后一行等同于:

return dec(decarg)(fun1)(decarg)

有了前面的解释,找到问题应该是微不足道的!

  • dec(decarg) 被执行并且 return 是一个“_dec”函数对象;请注意,此 "decarg" 是在第一个括号中传递的,因此在装饰器中传递。
  • _dec(fun1) 被执行并且 return 是一个“_fun”函数对象。
  • _fun(decarg) 被执行并使用 return 语句调用 fun1(decargs) ,这将正确转换为 fun1(3) ,这就是您得到的结果;请注意,此 "decarg" 是在第三个括号中传递的,因此当您在 main."fun1" 中调用 "fun1" 时。

你没有得到 13 作为结果,因为你没有用

的结果调用 "fun1"
funarg = decarg + 7

作为参数,而是用 "decarg" 调用它,它作为位置参数(funarg=decarg)从 main.

传递给“_fun”

无论如何,我要感谢你提出这个问题,因为我一直在寻找一种仅在调用函数时将参数传递给装饰器的巧妙方法,而且效果非常好。

这是另一个可能有帮助的例子:

from functools import wraps

def add(addend):
    def decorator(func):
        @wraps(func)
        def wrapper(p1, p2=101):
            for v in func(p1, p2):
                yield v + addend
        return wrapper
    return decorator


def mul(multiplier):
    def decorator(func):
        @wraps(func)
        def wrapper(p1, p2=101):
            for v in func(p1, p2):
                yield v * multiplier
        return wrapper
    return decorator


def super_gen(p1, p2=101, a=0, m=1):
    @add(a)
    @mul(m)
    def gen(p1, p2=101):
        for x in range(p1, p2):
            yield x
    return gen(p1, p2)

好的,这是一个简单的解释:

一个简单的装饰器(无参数)是一个函数,它接受一个函数作为参数,returns 将调用另一个函数来代替原来的函数

接受参数的装饰器是一个接受该参数的函数,returns一个简单的装饰器

在这里你可以构建一个简单的装饰器,将装饰函数的参数加7:

def add7(func):
    '''takes a func and returns a decorated func adding 7 to its parameter'''
    def resul(x):
        return func(x + 7)
    return resul

你可以这样使用它:

def main(mainarg):
    decarg = mainarg + 2
    @add7
    def fun1(funarg):
        return funarg+3
    return fun1(decarg)

main(1)

它 returns 符合预期 13.

但是你可以很容易地构建一个参数化装饰器,它将添加一个任意值,但是添加一个级别来处理参数:

def adder(incr):
    '''Process the parameter (the increment) and returns a simple decorator'''
    def innerdec(func):
        '''Decorator that takes a function and returns a decorated one
that will add the passed increment to its parameter'''
        def resul(val):
            return func(val + incr)
        return resul
    return innerdec

然后你就这样使用它

 def main(mainarg):
    decarg = mainarg + 2
    @adder(7)
    def fun1(funarg):
        return funarg + 3
    return fun1(decarg)

main(1)

还是returns13