首页 文章

带参数的装饰器?

提问于
浏览
282

我有装饰器传递变量'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

7 回答

  • 218

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

    def decorator(argument):
        def real_decorator(function):
            def wrapper(*args, **kwargs):
                funny_stuff()
                something_with_argument(argument)
                result = function(*args, **kwargs)
                more_funny_stuff()
                return result
            return wrapper
        return real_decorator
    

    Here您可以阅读有关该主题的更多信息 - 也可以使用可调用对象实现此功能,并在此处进行了解释 .

  • 49

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

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

    装饰函数的一个作用是在装饰器声明时重写名称 foo . foo 是"overridden"由 real_decorator 返回的任何内容 . 在这种情况下,一个新的函数对象 .

    所有 foo 的元数据都被覆盖,特别是docstring和函数名称 .

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

    functools.wraps为我们提供了一个方便的方法_文件名和返回函数的名称"lift" .

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

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

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

    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 作为包装函数,但我建议一直使用它 .

  • 0

    这是t.dubrownik's answer的略微修改版本 . 为什么?

    • 作为通用模板,您应该从原始函数返回返回值 .

    • 这会更改函数的名称,这可能会影响其他装饰器/代码 .

    所以使用@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
    
  • 64

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

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

    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.

  • 35

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

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

  • 0

    定义这个“decoratorize函数”来生成自定义的装饰器函数:

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

    以这种方式使用它:

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

相关问题