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

当前回答

以下是对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

其他回答

定义这个decoratorize函数来生成定制的decorator函数:

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

可以这样用:

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

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

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

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

but

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

在这里,我们用两个不同的名称和两个不同的年龄运行了两次display info。 现在,每当我们运行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 

现在让我们继续让decorator函数接受参数。 例如,假设我想为包装器中的所有这些打印语句添加一个可定制的前缀。 现在这将是一个很好的候选参数的装饰。 我们传入的参数就是那个前缀。现在为了做到这一点,我们将添加另一个外层到我们的装饰器中,我将把这个函数称为前缀装饰器。

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 

现在我们在包装器函数的print语句之前有了LOG:前缀,你可以随时更改它。

我想展示一个想法,在我看来很优雅。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。包装器函数,但我建议始终使用它。