带参数的装饰器?


401

我在装饰器传递变量'insurance_mode'时遇到问题。我可以通过以下装饰器语句来做到这一点:

 @execute_complete_reservation(True)
 def test_booking_gta_object(self):
     self.test_select_gta_object()

但不幸的是,该声明不起作用。也许也许有更好的方法来解决此问题。

def execute_complete_reservation(test_case,insurance_mode):
    def inner_function(self,*args,**kwargs):
        self.test_create_qsf_query()
        test_case(self,*args,**kwargs)
        self.test_select_room_option()
        if insurance_mode:
            self.test_accept_insurance_crosseling()
        else:
            self.test_decline_insurance_crosseling()
        self.test_configure_pax_details()
        self.test_configure_payer_details

    return inner_function

3
您的示例在语法上无效。execute_complete_reservation需要两个参数,但您要传递一个。装饰器只是将功能包装到其他功能中的语法糖。有关完整的文档,请参见docs.python.org/reference/compound_stmts.html#function
布莱恩·克拉珀

Answers:


687

带参数的装饰器的语法有些不同-带参数的装饰器应返回一个函数,该函数将接受一个函数并返回另一个函数。因此,它实际上应该返回一个普通的装饰器。有点混乱吧?我的意思是:

def decorator_factory(argument):
    def decorator(function):
        def wrapper(*args, **kwargs):
            funny_stuff()
            something_with_argument(argument)
            result = function(*args, **kwargs)
            more_funny_stuff()
            return result
        return wrapper
    return decorator

在这里,您可以阅读有关该主题的更多信息-也可以使用可调用对象来实现此目的,这也在那里进行了说明。


56
我想知道为什么GVR没有通过在“功能”之后将参数作为后续装饰器参数传入来实现它。“ Y,我听说你喜欢关闭……”等等。
MichelMüller2014年

3
>函数是第一个参数还是最后一个参数?显然,首先,因为参数是可变长度的参数列表。>怪异的是,您使用不同于定义的签名来“调用”该函数。正如您所指出的,它实际上非常合适-它与类方法的调用非常相似。为了更加清楚,您可以使用decorator(self_func,param1,...)约定。但请注意:我不主张在这里的任何变化,Python是太远了,道路,我们可以看到如何打破的变化制定了..
米歇尔穆勒

21
您忘记了非常有用的functools.wraps装饰包装器:)
socketpair

10
您在调用函数时忘记了返回,即 return function(*args, **kwargs)
formiaczek

36
也许很明显,但是以防万一:您需要将此装饰器用作,@decorator()而不仅是@decorator,即使您只有可选参数。
Patrick Mevzek '17

324

编辑:要深入了解装饰者的心理模型,请看一下这个很棒的Pycon Talk。非常值得30分钟。

考虑带有参数的装饰器的一种方法是

@decorator
def foo(*args, **kwargs):
    pass

转换为

foo = decorator(foo)

因此,如果装饰者有争论,

@decorator_with_args(arg)
def foo(*args, **kwargs):
    pass

转换为

foo = decorator_with_args(arg)(foo)

decorator_with_args 是一个接受自定义参数并返回实际装饰器的函数(将应用于装饰器函数)。

我使用带有局部选项的简单技巧来使我的装饰器变得容易

from functools import partial

def _pseudo_decor(fun, argument):
    def ret_fun(*args, **kwargs):
        #do stuff here, for eg.
        print ("decorator arg is %s" % str(argument))
        return fun(*args, **kwargs)
    return ret_fun

real_decorator = partial(_pseudo_decor, argument=arg)

@real_decorator
def foo(*args, **kwargs):
    pass

更新:

以上,foo成为real_decorator(foo)

装饰函数的一种效果是,名称foo在装饰器声明时被覆盖。foo被的返回值“覆盖” real_decorator。在这种情况下,一个新的功能对象。

的所有foo元数据都将被覆盖,特别是docstring和函数名称。

>>> print(foo)
<function _pseudo_decor.<locals>.ret_fun at 0x10666a2f0>

functools.wraps为我们提供了一种方便的方法,可将文档字符串和名称“提升”为返回的函数。

from functools import partial, wraps

def _pseudo_decor(fun, argument):
    # magic sauce to lift the name and doc of the function
    @wraps(fun)
    def ret_fun(*args, **kwargs):
        #do stuff here, for eg.
        print ("decorator arg is %s" % str(argument))
        return fun(*args, **kwargs)
    return ret_fun

real_decorator = partial(_pseudo_decor, argument=arg)

@real_decorator
def bar(*args, **kwargs):
    pass

>>> print(bar)
<function __main__.bar(*args, **kwargs)>

4
您的回答完美地说明了装饰器的固有正交性,谢谢您
zsf222

你能补充@functools.wraps吗?
Mr_and_Mrs_D

1
@Mr_and_Mrs_D,我使用的示例更新了帖子functool.wraps。在示例中添加它可能会使读者进一步困惑。
srj

7
这是什么arg
显示名

1
您如何将传递的参数传递bar给的参数real_decorator
Chang Zhao

85

我想展示一个想法,恕我直言,非常优雅。t.dubrownik提出的解决方案显示了一种始终不变的模式:无论装饰器做什么,都需要三层包装器。

所以我认为这是元装饰器的工作,即装饰器的装饰器。装饰器是一个函数,它实际上可以用作带有参数的常规装饰器:

def parametrized(dec):
    def layer(*args, **kwargs):
        def repl(f):
            return dec(f, *args, **kwargs)
        return repl
    return layer

可以将其应用于常规装饰器以添加参数。例如,假设我们有一个装饰器,它将一个函数的结果加倍:

def double(f):
    def aux(*xs, **kws):
        return 2 * f(*xs, **kws)
    return aux

@double
def function(a):
    return 10 + a

print function(3)    # Prints 26, namely 2 * (10 + 3)

通过@parametrized我们可以构建@multiply具有参数的通用装饰器

@parametrized
def multiply(f, n):
    def aux(*xs, **kws):
        return n * f(*xs, **kws)
    return aux

@multiply(2)
def function(a):
    return 10 + a

print function(3)    # Prints 26

@multiply(3)
def function_again(a):
    return 10 + a

print function(3)          # Keeps printing 26
print function_again(3)    # Prints 39, namely 3 * (10 + 3)

通常,a的第一个参数 装饰器参数是函数,而其余参数将对应于参数化装饰器的参数。

一个有趣的用法示例可以是类型安全的断言修饰符:

import itertools as it

@parametrized
def types(f, *types):
    def rep(*args):
        for a, t, n in zip(args, types, it.count()):
            if type(a) is not t:
                raise TypeError('Value %d has not type %s. %s instead' %
                    (n, t, type(a))
                )
        return f(*args)
    return rep

@types(str, int)  # arg1 is str, arg2 is int
def string_multiply(text, times):
    return text * times

print(string_multiply('hello', 3))    # Prints hellohellohello
print(string_multiply(3, 3))          # Fails miserably with TypeError

最后一点:这里我没有使用functools.wraps包装函数,但是我建议您一直使用它。


3
没有完全使用它,而是帮助我理解了这个概念:)谢谢!
mouckatron

我尝试了一下,遇到了一些问题
杰夫(Jeff)

@Jeff您可以与我们分享您遇到的问题吗?
Dacav '17

我把它与我的问题联系在一起,我确实知道了...我需要@wraps针对我的特殊情况打电话给我。
杰夫(Jeff)

4
噢,天哪,我为此整整失去了一天。值得庆幸的是,我遇到了这个答案(顺便说一句,这可能是整个互联网上创造的最好的答案)。他们也使用你的@parametrized把戏。我遇到的问题是我忘记了@语法等于实际的调用(以某种方式我知道了这一点,但同时又不知道可以从我的问题中收集到的信息)。因此,如果您想将@语法转换为平凡的调用以检查其工作方式,则最好先暂时将其注释掉,否则最终将其调用两次并得到mumbojumbo的结果
z33k,2016年

79

这是t.dubrownik的答案的略微修改版本。为什么?

  1. 作为常规模板,您应该从原始函数返回返回值。
  2. 这会更改函数的名称,这可能会影响其他修饰符/代码。

因此使用@functools.wraps()

from functools import wraps

def decorator(argument):
    def real_decorator(function):
        @wraps(function)
        def wrapper(*args, **kwargs):
            funny_stuff()
            something_with_argument(argument)
            retval = function(*args, **kwargs)
            more_funny_stuff()
            return retval
        return wrapper
    return real_decorator

37

我想您的问题是将参数传递给装饰器。这有点棘手,并不简单。

这是如何执行此操作的示例:

class MyDec(object):
    def __init__(self,flag):
        self.flag = flag
    def __call__(self, original_func):
        decorator_self = self
        def wrappee( *args, **kwargs):
            print 'in decorator before wrapee with flag ',decorator_self.flag
            original_func(*args,**kwargs)
            print 'in decorator after wrapee with flag ',decorator_self.flag
        return wrappee

@MyDec('foo de fa fa')
def bar(a,b,c):
    print 'in bar',a,b,c

bar('x','y','z')

印刷品:

in decorator before wrapee with flag  foo de fa fa
in bar x y z
in decorator after wrapee with flag  foo de fa fa

有关更多详细信息,请参见Bruce Eckel的文章。


20
当心装饰器类。除非您手动重新创建实例方法描述符的逻辑,否则它们对方法无效。

9
delnan,想详细说明吗?我只需要使用一次这种模式,所以我还没有遇到任何陷阱。
罗斯·罗杰斯

2
@RossRogers我的猜测是@delnan指__name__的是装饰器类的实例将没有的东西?
jamesc 2014年

9
@jamesc那也很容易解决。我所指的具体情况是class Foo: @MyDec(...) def method(self, ...): blah不起作用,因为它不是Foo().method绑定方法,也不会self自动通过。也可以通过创建MyDec一个描述符并在中创建绑定方法来解决此问题__get__,但是它涉及更多且不那么明显。最后,装饰器类并不像看起来那样方便。

2
@delnan我希望看到此警告更为突出。我正在努力,并且有兴趣看到确实可行的解决方案(虽然可能涉及的程度较小,但涉及的程度却不那么明显)。
HaPsantran '16

12
def decorator(argument):
    def real_decorator(function):
        def wrapper(*args):
            for arg in args:
                assert type(arg)==int,f'{arg} is not an interger'
            result = function(*args)
            result = result*argument
            return result
        return wrapper
    return real_decorator

装饰器的用法

@decorator(2)
def adder(*args):
    sum=0
    for i in args:
        sum+=i
    return sum

然后

adder(2,3)

产生

10

adder('hi',3)

产生

---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-143-242a8feb1cc4> in <module>
----> 1 adder('hi',3)

<ipython-input-140-d3420c248ebd> in wrapper(*args)
      3         def wrapper(*args):
      4             for arg in args:
----> 5                 assert type(arg)==int,f'{arg} is not an interger'
      6             result = function(*args)
      7             result = result*argument

AssertionError: hi is not an interger

8

这是用于函数装饰器的模板,该模板不需要提供()任何参数即可:

import functools


def decorator(x_or_func=None, *decorator_args, **decorator_kws):
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kws):
            if 'x_or_func' not in locals() \
                    or callable(x_or_func) \
                    or x_or_func is None:
                x = ...  # <-- default `x` value
            else:
                x = x_or_func
            return func(*args, **kws)

        return wrapper

    return _decorator(x_or_func) if callable(x_or_func) else _decorator

下面是一个示例:

def multiplying(factor_or_func=None):
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            if 'factor_or_func' not in locals() \
                    or callable(factor_or_func) \
                    or factor_or_func is None:
                factor = 1
            else:
                factor = factor_or_func
            return factor * func(*args, **kwargs)
        return wrapper
    return _decorator(factor_or_func) if callable(factor_or_func) else _decorator


@multiplying
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying()
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying(10)
def summing(x): return sum(x)

print(summing(range(10)))
# 450

还需要注意的是factor_or_func(或任何其它参数)不应该被重新分配wrapper()
norok2

为什么需要办理登机手续locals()
Shital Shah

@ShitalShah涵盖了不使用装饰器的情况()
norok2

4

在我的实例中,我决定通过单行lambda解决此问题,以创建一个新的装饰器函数:

def finished_message(function, message="Finished!"):

    def wrapper(*args, **kwargs):
        output = function(*args,**kwargs)
        print(message)
        return output

    return wrapper

@finished_message
def func():
    pass

my_finished_message = lambda f: finished_message(f, "All Done!")

@my_finished_message
def my_func():
    pass

if __name__ == '__main__':
    func()
    my_func()

执行后,将打印:

Finished!
All Done!

也许没有其他解决方案可扩展,但是为我工作。


这可行。尽管可以,但是很难将值设置给装饰器。
Arindam Roychowdhury,

3

编写一个可以使用和不使用参数的装饰器是一个挑战,因为Python在这两种情况下期望完全不同的行为!许多答案都试图解决此问题,以下是@ norok2对答案的改进。具体来说,这种变化消除了对的使用locals()

遵循@ norok2给出的相同示例:

import functools

def multiplying(f_py=None, factor=1):
    assert callable(f_py) or f_py is None
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return factor * func(*args, **kwargs)
        return wrapper
    return _decorator(f_py) if callable(f_py) else _decorator


@multiplying
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying()
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying(factor=10)
def summing(x): return sum(x)

print(summing(range(10)))
# 450

玩这个代码

要注意的是,用户必须提供键,值对参数而不是位置参数,并且保留第一个参数。


2

众所周知,以下两段代码几乎等效:

@dec
def foo():
    pass    foo = dec(foo)

############################################
foo = dec(foo)

一个常见的错误是认为它@只是隐藏了最左边的参数。

@dec(1, 2, 3)
def foo():
    pass    
###########################################
foo = dec(foo, 1, 2, 3)

如果上面是这样的话,编写装饰器会容易得多@。不幸的是,这不是事情的完成方式。


考虑一个装饰器Wait,它会破坏程序执行几秒钟。如果您未通过等待时间,则默认值为1秒。用例如下所示。

##################################################
@Wait
def print_something(something):
    print(something)

##################################################
@Wait(3)
def print_something_else(something_else):
    print(something_else)

##################################################
@Wait(delay=3)
def print_something_else(something_else):
    print(something_else)

Wait具有参数(例如)时@Wait(3),将其他任何事情发生之前Wait(3) 执行调用。

也就是说,以下两段代码是等效的

@Wait(3)
def print_something_else(something_else):
    print(something_else)

###############################################
return_value = Wait(3)
@return_value
def print_something_else(something_else):
    print(something_else)

这是个问题。

if `Wait` has no arguments:
    `Wait` is the decorator.
else: # `Wait` receives arguments
    `Wait` is not the decorator itself.
    Instead, `Wait` ***returns*** the decorator

一种解决方案如下所示:

让我们从创建以下类开始DelayedDecorator

class DelayedDecorator:
    def __init__(i, cls, *args, **kwargs):
        print("Delayed Decorator __init__", cls, args, kwargs)
        i._cls = cls
        i._args = args
        i._kwargs = kwargs
    def __call__(i, func):
        print("Delayed Decorator __call__", func)
        if not (callable(func)):
            import io
            with io.StringIO() as ss:
                print(
                    "If only one input, input must be callable",
                    "Instead, received:",
                    repr(func),
                    sep="\n",
                    file=ss
                )
                msg = ss.getvalue()
            raise TypeError(msg)
        return i._cls(func, *i._args, **i._kwargs)

现在我们可以编写如下内容:

 dec = DelayedDecorator(Wait, delay=4)
 @dec
 def delayed_print(something):
    print(something)

注意:

  • dec 不接受多个参数。
  • dec 仅接受要包装的功能。

    导入检查类PolyArgDecoratorMeta(type):def 调用(等待,* args,** kwargs):尝试:arg_count = len(args)if(arg_count == 1):如果callable(args [0]):SuperClass = inspect。 getmro(PolyArgDecoratorMeta)[1] r =超类。呼叫(Wait,args [0])否则:r = DelayedDecorator(等待,* args,** kwargs)否则:r = DelayedDecorator(等待,* args,** kwargs)最后:通过return r

    导入时间类Wait(metaclass = PolyArgDecoratorMeta):def init(i,func,delay = 2):i._func = func i._delay = delay

    def __call__(i, *args, **kwargs):
        time.sleep(i._delay)
        r = i._func(*args, **kwargs)
        return r 

以下两段代码是等效的:

@Wait
def print_something(something):
     print (something)

##################################################

def print_something(something):
    print(something)
print_something = Wait(print_something)

我们可以"something"非常缓慢地打印到控制台,如下所示:

print_something("something")

#################################################
@Wait(delay=1)
def print_something_else(something_else):
    print(something_else)

##################################################
def print_something_else(something_else):
    print(something_else)

dd = DelayedDecorator(Wait, delay=1)
print_something_else = dd(print_something_else)

##################################################

print_something_else("something")

最后说明

它可能看起来像一个大量的代码,但你不必写类DelayedDecoratorPolyArgDecoratorMeta每一个时间。您唯一需要亲自编写类似以下内容的代码,这很短:

from PolyArgDecoratorMeta import PolyArgDecoratorMeta
import time
class Wait(metaclass=PolyArgDecoratorMeta):
 def __init__(i, func, delay = 2):
     i._func = func
     i._delay = delay

 def __call__(i, *args, **kwargs):
     time.sleep(i._delay)
     r = i._func(*args, **kwargs)
     return r

1

定义此“ decoratorize函数”以生成定制的装饰器函数:

def decoratorize(FUN, **kw):
    def foo(*args, **kws):
        return FUN(*args, **kws, **kw)
    return foo

使用这种方式:

    @decoratorize(FUN, arg1 = , arg2 = , ...)
    def bar(...):
        ...

1

上面的好答案。此示例还说明了@wraps,它从原始函数中获取doc字符串和函数名,并将其应用于新的包装版本:

from functools import wraps

def decorator_func_with_args(arg1, arg2):
    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            print("Before orginal function with decorator args:", arg1, arg2)
            result = f(*args, **kwargs)
            print("Ran after the orginal function")
            return result
        return wrapper
    return decorator

@decorator_func_with_args("foo", "bar")
def hello(name):
    """A function which prints a greeting to the name provided.
    """
    print('hello ', name)
    return 42

print("Starting script..")
x = hello('Bob')
print("The value of x is:", x)
print("The wrapped functions docstring is:", hello.__doc__)
print("The wrapped functions name is:", hello.__name__)

印刷品:

Starting script..
Before orginal function with decorator args: foo bar
hello  Bob
Ran after the orginal function
The value of x is: 42
The wrapped functions docstring is: A function which prints a greeting to the name provided.
The wrapped functions name is: hello

0

如果函数和装饰器都必须接受参数,则可以采用以下方法。

例如,有一个名为的装饰器decorator1,它接受一个参数

@decorator1(5)
def func1(arg1, arg2):
    print (arg1, arg2)

func1(1, 2)

现在,如果decorator1参数必须是动态的,或者在调用函数时传递的,

def func1(arg1, arg2):
    print (arg1, arg2)


a = 1
b = 2
seconds = 10

decorator1(seconds)(func1)(a, b)

在上面的代码中

  • seconds 是为 decorator1
  • a, b 是...的论点 func1
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.