带参数的装饰器?

时间:2011-05-08 17:40:09

标签: python decorator

装饰器传输变量'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

17 个答案:

答案 0 :(得分:562)

你的意思是def test_booking_gta_object,对吧?无论如何,带参数的装饰器的语法有点不同 - 带参数的装饰器应该返回一个函数,它将获取一个函数并返回另一个函数。所以它应该真正返回一个普通的装饰器。有点混乱,对吧?我的意思是:

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

Here你可以阅读更多关于这个主题的内容 - 也可以使用可调用的对象实现这一点,并在那里解释。

答案 1 :(得分:250)

编辑:要深入了解装饰者的心理模型,请查看this精彩的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是一个接受自定义参数并返回实际装饰器(将应用于装饰函数)的函数。

我使用partials的简单技巧使我的装饰器变得容易

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)

装饰函数的一个作用是在装饰器声明时覆盖名称foofoo被覆盖""通过real_decorator返回的任何内容。在这种情况下,一个新的功能对象。

覆盖了所有foo个元数据,尤其是文档字符串和函数名称。

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

functools.wraps为我们提供了一种方便的方法来提升&#34;返回函数的docstring和name。

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

答案 2 :(得分:71)

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

所以我认为这是一个meta-decorator的工作,也就是装饰器的装饰器。由于装饰器是一个函数,它实际上作为带有参数的常规装饰器:

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)

传统上,参数化装饰器的第一个参数是函数,而其余参数将对应于参数化装饰器的参数。

一个有趣的用法示例可能是类型安全的断言装饰器:

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 :(得分:57)

以下是t.dubrownik's answer的略微修改版本。为什么呢?

  1. 作为一般模板,您应该从原始函数返回返回值。
  2. 这会更改函数的名称,这可能会影响其他装饰器/代码。
  3. 因此请使用@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
    

答案 4 :(得分:35)

我认为你的问题是将参数传递给你的装饰者。这有点棘手,并不简单。

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

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

See Bruce Eckel's article for more details.

答案 5 :(得分:12)

编写一个带有和不带有参数的装饰器是一个挑战,因为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

Play with this code

问题在于,用户必须提供键,值对参数而不是位置参数,并且保留第一个参数。

答案 6 :(得分:7)

这是用于函数修饰器的模板,如果不提供任何参数,则不需要()

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

答案 7 :(得分:4)

enter image description here

  • 在这里,我们用两个不同的名字和两个不同的年龄运行了两次显示信息。
  • 现在,每次我们运行 display info 时,我们的装饰器还添加了在包装函数之前和之后打印一行的功能。
def decorator_function(original_function):
    def wrapper_function(*args, **kwargs):
        print('Executed Before', original_function.__name__)
        result = original_function(*args, **kwargs)
        print('Executed After', original_function.__name__, '\n')
        return result
    return wrapper_function


@decorator_function
def display_info(name, age):
    print('display_info ran with arguments ({}, {})'.format(name, age))


display_info('Mr Bean', 66)
display_info('MC Jordan', 57)

输出:

Executed Before display_info
display_info ran with arguments (Mr Bean, 66)
Executed After display_info 

Executed Before display_info
display_info ran with arguments (MC Jordan, 57)
Executed After display_info 
  • 那么现在让我们继续让我们的装饰器函数接受参数。

  • 例如,假设我想要包装器中所有这些打印语句的可自定义前缀。

  • 现在这将是装饰器参数的一个很好的候选。

  • 我们传入的参数就是那个前缀。现在为了做到这一点,我们只是要为我们的装饰器添加另一个外层,所以我将把这个函数称为前缀装饰器。

def prefix_decorator(prefix):
    def decorator_function(original_function):
        def wrapper_function(*args, **kwargs):
            print(prefix, 'Executed Before', original_function.__name__)
            result = original_function(*args, **kwargs)
            print(prefix, 'Executed After', original_function.__name__, '\n')
            return result
        return wrapper_function
    return decorator_function


@prefix_decorator('LOG:')
def display_info(name, age):
    print('display_info ran with arguments ({}, {})'.format(name, age))


display_info('Mr Bean', 66)
display_info('MC Jordan', 57)

输出:

LOG: Executed Before display_info
display_info ran with arguments (Mr Bean, 66)
LOG: Executed After display_info 

LOG: Executed Before display_info
display_info ran with arguments (MC Jordan, 57)
LOG: Executed After display_info 
  • 现在我们在包装函数中的打印语句之前有 LOG: 前缀,您可以随时更改它。

答案 8 :(得分:4)

就这么简单

def real_decorator(any_number_of_arguments):
   def pseudo_decorator(function_to_be_decorated):

       def real_wrapper(function_arguments):
           print(function_arguments)
           result = function_to_be_decorated(any_number_of_arguments)
           return result

       return real_wrapper
   return pseudo_decorator

现在

@real_decorator(any_number_of_arguments)
def some_function(function_arguments):
        return "Any"

答案 9 :(得分:3)

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

答案 10 :(得分:2)

上面的好答案。这也说明了@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

答案 11 :(得分:2)

在我的实例中,我决定通过单行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!

也许不像其他解决方案那样可扩展,但为我工作。

答案 12 :(得分:1)

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

@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 call (请等待,* args,** kwargs):         尝试:             arg_count = len(args)             如果(arg_count == 1):                 如果可调用(参数[0]):                     超类= inspect.getmro(PolyArgDecoratorMeta)[1]                     r =超类。调用(等待,参数[0])                 其他:                     r = DelayedDecorator(等待,* args,** kwargs)             其他:                 r = DelayedDecorator(等待,* args,** kwargs)         最后:             通过         返回r

    导入时间 类Wait(metaclass = PolyArgDecoratorMeta):     def init (i,func,delay = 2):         i._func =函数         i._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

答案 13 :(得分:1)

这是一个使用带参数的装饰器的 Flask 示例。假设我们有一个路由“/user/name”,我们想映射到他的主页。

def matchR(dirPath):
    def decorator(func):
        def wrapper(msg):
            if dirPath[0:6] == '/user/':
                print(f"User route '{dirPath}' match, calling func {func}")
                name = dirPath[6:]
                return func(msg2=name, msg3=msg)
            else:
                print(f"Input dirPath '{dirPath}' does not match route '/user/'")
                return
        return  wrapper
    return decorator

#@matchR('/Morgan_Hills')
@matchR('/user/Morgan_Hills')
def home(**kwMsgs):
    for arg in kwMsgs:
        if arg == 'msg2':
            print(f"In home({arg}): Hello {kwMsgs[arg]}, welcome home!")
        if arg == 'msg3':
            print(f"In home({arg}): {kwMsgs[arg]}")

home('This is your profile rendered as in index.html.')

输出:

User route '/user/Morgan_Hills' match, calling func <function home at 0x000001DD5FDCD310>
In home(msg2): Hello Morgan_Hills, welcome home!
In home(msg3): This is your profile rendered as in index.html.

答案 14 :(得分:0)

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

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

以这种方式使用它:

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

答案 15 :(得分:0)

是一个可以通过多种方式调用的装饰器(在python3.7中测试过):

import functools


def my_decorator(*args_or_func, **decorator_kwargs):

    def _decorator(func):

        @functools.wraps(func)
        def wrapper(*args, **kwargs):

            if not args_or_func or callable(args_or_func[0]):
                # Here you can set default values for positional arguments
                decorator_args = ()
            else:
                decorator_args = args_or_func

            print(
                "Available inside the wrapper:",
                decorator_args, decorator_kwargs
            )

            # ...
            result = func(*args, **kwargs)
            # ...

            return result

        return wrapper

    return _decorator(args_or_func[0]) \
        if args_or_func and callable(args_or_func[0]) else _decorator


@my_decorator
def func_1(arg): print(arg)

func_1("test")
# Available inside the wrapper: () {}
# test


@my_decorator()
def func_2(arg): print(arg)

func_2("test")
# Available inside the wrapper: () {}
# test


@my_decorator("any arg")
def func_3(arg): print(arg)

func_3("test")
# Available inside the wrapper: ('any arg',) {}
# test


@my_decorator("arg_1", 2, [3, 4, 5], kwarg_1=1, kwarg_2="2")
def func_4(arg): print(arg)

func_4("test")
# Available inside the wrapper: ('arg_1', 2, [3, 4, 5]) {'kwarg_1': 1, 'kwarg_2': '2'}
# test

感谢用户 @norok2 - https://stackoverflow.com/a/57268935/5353484

UPD 装饰器,用于根据注释验证类的函数和方法的参数和/或结果。可用于同步或异步版本:https://github.com/EvgeniyBurdin/valdec

答案 16 :(得分:-1)

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

例如,有一个名为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)

在上面的代码中

  • secondsdecorator1的参数
  • a, bfunc1
  • 的参数