我有一个由装饰器转移变量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提出的解决方案显示了一个始终相同的模式:无论装饰器做什么,您都需要三层包装器。

所以我认为这是一个元装饰师的工作,也就是说,装饰师的装饰师。由于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。包装器函数,但我建议始终使用它。

其他回答

带参数的装饰器的语法有点不同——带参数的装饰器应该返回一个函数,该函数将接受一个函数并返回另一个函数。它应该返回一个普通的装饰器。有点困惑,对吧?我的意思是:

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

在这里你可以读到更多关于这个主题的内容——也可以使用可调用对象来实现这个功能,这里也有解释。

匿名设置中的参数装饰。

在许多可能的“嵌套”语法糖装饰的两种变化中被提出。它们之间的区别在于执行wrt到目标函数的顺序,并且它们的效果通常是独立的(不相互作用)。

装饰器允许在目标函数执行之前或之后“注入”自定义函数。

这两个函数的调用都发生在一个元组中。默认情况下,返回值是目标函数的结果。

语法糖装饰@first_internal(send_msg)('…end')要求版本>= 3.9,请参阅PEP 614放松对装饰器的语法限制。

functools使用。以保留目标函数的文档字符串。

from functools import wraps


def first_external(f_external):
    return lambda *args_external, **kwargs_external:\
           lambda f_target: wraps(f_target)(
               lambda *args_target, **kwargs_target:
                  (f_external(*args_external, **kwargs_external),
                   f_target(*args_target, **kwargs_target))[1]
           )


def first_internal(f_external):
    return lambda *args_external, **kwargs_external:\
           lambda f_target: wraps(f_target)(
               lambda *args_target, **kwargs_target:
                  (f_target(*args_target, **kwargs_target),
                   f_external(*args_external, **kwargs_external))[0]
           )


def send_msg(x):
   print('msg>', x)


@first_internal(send_msg)('...end')    # python >= 3.9
@first_external(send_msg)("start...")  # python >= 3.9
def test_function(x):
    """Test function"""
    print('from test_function')
    return x


test_function(2)

输出

msg> start...
from test_function
msg> ...end

讲话

composition decorators, such as pull-back and push-forward (maybe in a more Computer Science terminology: co- and resp. contra-variant decorator), could more useful but need ad-hoc care, for example composition rules, check which parameters go where, etc syntactic sugar acts as a kind of partial of the target function: once decorated there is no way back (without extra imports) but it is not mandatory, a decorator can be used also in its extended forms, i.e. first_external(send_msg)("start...")(test_function)(2) the results of a workbench with timeit.repeat(..., repeat=5, number=10000) which compare the classical def and lambda decoration shows that are almost equivalent: for lambda: [6.200810984999862, 6.035239247000391, 5.346362481000142, 5.987880147000396, 5.5331550319997405] - mean -> 5.8206 for def: [6.165001932999985, 5.554595884999799, 5.798066574999666, 5.678178028000275, 5.446507932999793] - mean -> 5.7284 naturally an non-anonymous counterpart is possible and provides more flexibility

我猜你的问题是把参数传递给你的装饰师。这有点棘手,不简单。

下面是一个如何做到这一点的例子:

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的文章。

这是curry函数的一个很好的用例。

curry函数本质上是延迟函数的调用,直到提供了所有输入。

这可以用于各种事情,如包装器或函数式编程。在本例中,让我们创建一个接受输入的包装器。

我将使用一个简单的包pamda,其中包含一个用于python的curry函数。这可以用作其他函数的包装器。

安装 Pamda:

pip install pamda

创建一个简单的带有两个输入的装饰函数:

@pamda.curry()
def my_decorator(input, func):
    print ("Executing Decorator")
    print(f"input:{input}")
    return func

使用提供给目标函数的第一个输入应用你的装饰器:

@my_decorator('Hi!')
def foo(input):
    print('Executing Foo!')
    print(f"input:{input}")

执行你的包装函数:

x=foo('Bye!')

把所有东西放在一起:

from pamda import pamda

@pamda.curry()
def my_decorator(input, func):
    print ("Executing Decorator")
    print(f"input:{input}")
    return func

@my_decorator('Hi!')
def foo(input):
    print('Executing Foo!')
    print(f"input:{input}")

x=foo('Bye!')

将:

Executing Decorator
input:Hi!
Executing Foo!
input:Bye!

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