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

时间:2015-10-17 19:08:44

标签: python python-decorators

我最近决定学习Python装饰器,并遵循this howto。特别是在“向装饰者传递参数”一节中的例子。我想要做的是(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)

当我期望13时,返回6.我希望main()中的参数增加2,_dec()中增加7,这应该将此变量传递给修饰函数,后者增加3它。

之后,我阅读了thisthis 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)

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

2 个答案:

答案 0 :(得分:5)

在考虑装饰器如何转换函数以及函数本身是Python中的对象之后,原因立即发生。

让我们从后者开始。

功能是对象:

当我们考虑函数名后两对括号的含义时,这是立竿见影的。考虑这个简单的例子(Python 3):

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

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

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

func(5)(10)

您可以将此视为首先调用

func(5)

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

func2(10)

既然定义了“x”和“y”,“func2”可以将最终值返回“result”。

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

func2

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

func2()

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

<强> 装修:

在您的示例中,“main”在

的最后一行调用“fun1”
return fun1(decarg)

由于装饰者

@dec(decarg)

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

fun1 = dec(decarg)(fun1)

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

return dec(decarg)(fun1)(decarg)

根据之前的解释,找到问题应该是微不足道的!

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

因为你没有使用

的结果调用“fun1”,所以你得不到13
funarg = decarg + 7

作为参数,而是用“decarg”调用它,它从main传递给“_fun”作为位置参数(funarg = decarg)。

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

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

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)

答案 1 :(得分:1)

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

一个简单的装饰器(无参数)是一个函数,它将一个函数作为参数并返回另一个将被调用的函数来代替原始函数

接受参数的装饰器是一个接受该参数并返回一个简单装饰器的函数

在这里,您可以构建一个简单的装饰器,将装饰函数的参数添加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)

按预期返回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)

仍然返回13