我有一个由装饰器转移变量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

当前回答

上面的回答很棒。这个例子还演示了@wraps,它从原始函数中获取文档字符串和函数名,并将其应用于新的包装版本:

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

其他回答

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

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

以下是对t.dubrownik的回答稍加修改的版本。为什么?

作为通用模板,您应该返回原始函数的返回值。 这将改变函数的名称,这可能会影响其他装饰器/代码。

所以使用@functools.wraps():

from functools import wraps

def create_decorator(argument):
    def 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 decorator

假设你有一个函数

def f(*args):
    print(*args)

你想要添加一个接受参数的装饰器,就像这样:

@decorator(msg='hello')
def f(*args):
    print(*args)

这意味着Python将对f进行如下修改:

f = decorator(msg='hello')(f)

因此,部件装饰器(msg='hello')的返回值应该是一个包装器函数,它接受函数f并返回修改后的函数。然后可以执行修改后的函数。

def decorator(**kwargs):
    def wrap(f):
        def modified_f(*args):
            print(kwargs['msg']) # use passed arguments to the decorator
            return f(*args)
        return modified_f
    return wrap

所以,当你调用f时,就像你在做: 装饰(味精= '你好')(f) (args) === wrap(f)(args) === modified_f(args) 但是modified_f可以访问传递给装饰器的kwargs

的输出

f(1,2,3)

将会是:

hello
(1, 2, 3)

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

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

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

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

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)

使用@ parameterized,我们可以构建一个带参数的通用@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。包装器函数,但我建议始终使用它。

编辑:为了深入了解装饰师的心理模型,请看看这个很棒的Pycon Talk。这30分钟很值得。

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

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

翻译为

foo = decorator(foo)

如果decorator有参数,

@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的所有元数据都会被重写,尤其是文档字符串和函数名。

>>> 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):
        # pre function execution stuff here, for eg.
        print("decorator argument is %s" % str(argument))
        returned_value =  fun(*args, **kwargs)
        # post execution stuff here, for eg.
        print("returned value is %s" % returned_value)
        return returned_value

    return ret_fun

real_decorator1 = partial(_pseudo_decor, argument="some_arg")
real_decorator2 = partial(_pseudo_decor, argument="some_other_arg")

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

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

>>> bar(1,2,3, k="v", x="z")
decorator argument is some_arg
returned value is None