如何将参数传递给装饰器,在那里处理,并转发到装饰函数?

dee*_*nes 6 python python-decorators

我最近决定学习Python装饰器,并遵循这个方法.特别是在"向装饰者传递参数"一节中的例子.我想要做的是(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)
Run Code Online (Sandbox Code Playgroud)

这将返回6,当我期望13.我希望参数增加2 main()和7 in _dec(),这应该将此变量传递给装饰函数,它将3添加到它.

之后,我读了这个这个 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)
Run Code Online (Sandbox Code Playgroud)

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

Ber*_*one 5

在考虑装饰器如何转换函数以及函数本身是Python对象时,其原因是直接的。

让我们从后者开始。

函数是对象:

当我们考虑函数名称后的两对括号的含义时,这是立即的。考虑以下简单示例(Python 3):

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

result = func(5)(10)
print(result)  # 15
Run Code Online (Sandbox Code Playgroud)

在这里,“ func”返回函数对象“ func2”,因此可以使用:

func(5)(10)
Run Code Online (Sandbox Code Playgroud)

您可以将其视为先致电

func(5)
Run Code Online (Sandbox Code Playgroud)

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

func2(10)
Run Code Online (Sandbox Code Playgroud)

现在,由于同时定义了“ x”和“ y”,因此“ func2”可以将最终值返回“ result”。

记住,这一切都是可能的,因为函数本身就是对象,并且因为“ func”返回一个函数对象

func2
Run Code Online (Sandbox Code Playgroud)

不是它的结果(它不是单独调用该函数)

func2()
Run Code Online (Sandbox Code Playgroud)

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

装饰器:

在您的示例中,“ main”在最后一行中调用“ fun1”

return fun1(decarg)
Run Code Online (Sandbox Code Playgroud)

由于装饰

@dec(decarg)
Run Code Online (Sandbox Code Playgroud)

实际上,您可以将“ fun1”视为:

fun1 = dec(decarg)(fun1)
Run Code Online (Sandbox Code Playgroud)

因此,“ main”中的最后一行等效于:

return dec(decarg)(fun1)(decarg)
Run Code Online (Sandbox Code Playgroud)

通过前面的解释,发现问题应该很简单!

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

结果不会得到13,因为您不会使用以下结果调用“ fun1”

funarg = decarg + 7
Run Code Online (Sandbox Code Playgroud)

作为参数,但是您可以使用“ 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)
Run Code Online (Sandbox Code Playgroud)