关于python:带参数的装饰器?

Decorators with parameters?

我有一个问题的变量'保险模式'的转移装饰。我可以通过下面的decorator语句来实现:

1
2
3
 @execute_complete_reservation(True)
 def test_booking_gta_object(self):
     self.test_select_gta_object()

但不幸的是,这种说法不起作用。也许有更好的方法来解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
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


你是说def test_booking_gta_object对吧?无论如何,带参数的decorator的语法有点不同-带参数的decorator应该返回一个接受函数并返回另一个函数的函数。所以它真的应该返回一个正常的装饰器。有点困惑,对吧?我的意思是:

1
2
3
4
5
6
7
8
9
10
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

在这里,您可以阅读更多关于这个主题的内容——也可以使用可调用对象来实现这一点,这里也解释了这一点。


编辑:为了深入了解装饰师的思维模式,请看这篇精彩的Pycon演讲。很值得30分钟。

有一种思考装饰师的方法是

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

转换为

1
foo = decorator(foo)

所以如果装饰师有争论,

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

转换为

1
foo = decorator_with_args(arg)(foo)

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

我用一个简单的技巧加上部分使我的装饰工容易些。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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在decorator声明中被重写。fooreal_decorator返回的内容"覆盖"。在本例中,是一个新的函数对象。

所有foo的元数据都被重写,特别是docstring和函数名。

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

functools.wrapps为我们提供了一个方便的方法来"提升"返回函数的docstring和名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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)>


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

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

1
2
3
4
5
6
def parametrized(dec):
    def layer(*args, **kwargs):
        def repl(f):
            return dec(f, *args, **kwargs)
        return repl
    return layer

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

1
2
3
4
5
6
7
8
9
10
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装饰器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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)

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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作为包装函数,但我建议您一直使用它。


这是T.Dubrownik答案的一个稍微修改过的版本。为什么?

  • 作为一般模板,您应该从原始函数返回返回值。
  • 这会更改函数的名称,这可能会影响其他装饰器/代码。
  • 所以使用@functools.wraps()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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


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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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')

    印刷品:

    1
    2
    3
    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

    更多详情请参阅布鲁斯·埃克尔的文章。


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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

    装饰器的使用

    1
    2
    3
    4
    5
    6
    @decorator(2)
    def adder(*args):
        sum=0
        for i in args:
            sum+=i
        return sum

    然后

    1
    adder(2,3)

    生产

    1
    10

    但是

    1
    adder('hi',3)

    生产

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    ---------------------------------------------------------------------------
    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


    在我的例子中,我决定通过一行lambda来解决这个问题,以创建一个新的decorator函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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()

    执行时,打印:

    1
    2
    Finished!
    All Done!

    也许不像其他解决方案那样可扩展,但对我有用。


    定义此"decoratorize函数"以生成自定义的decorator函数:

    1
    2
    3
    4
    def decoratorize(FUN, **kw):
        def foo(*args, **kws):
            return FUN(*args, **kws, **kw)
        return foo

    使用方法如下:

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