带参数的装饰器?

fal*_*cin 361 python decorator

我有装饰器传递变量'insurance_mode'的问题.我会通过以下装饰器声明来做到这一点:

 @execute_complete_reservation(True)
 def test_booking_gta_object(self):
     self.test_select_gta_object()
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

小智 624

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

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
Run Code Online (Sandbox Code Playgroud)

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

  • 我想知道为什么GVR没有通过在'function'之后传递参数作为后续装饰器参数来实现它.'哟dawg我听说你喜欢关闭......'等等. (48认同)
  • 也许显而易见,但以防万一:你需要使用这个装饰器作为`@decorator()`而不仅仅是`@ decorator`,即使你只有可选参数. (27认同)
  • 你忘记了非常有用的functools.wraps用于装饰包装:) (18认同)
  • 你在调用函数时忘记了返回,即`return function(*args,**kwargs)` (10认同)
  • 为了完成这个,你可能还想在 `def wrapper...` 行上方添加 `@functools.wraps(function)`。 (3认同)
  • 我只是在整个地方做了lambdas.(阅读:Python太棒了!):) (2认同)
  • >函数是第一个参数还是最后一个参数?显然,首先,因为参数是可变长度的参数列表。>怪异的是,您使用不同于定义的签名来“调用”该函数。正如您所指出的,它实际上非常合适-它与类方法的调用非常相似。为了更加清楚,您可以使用decorator(self_func,param1,...)约定。但是请注意:我不主张在此进行任何更改,Python的实现还远远不够,我们可以看到突破性的更改是如何解决的。 (2认同)
  • @Titan 我想说它非常 pythoninc。嵌套方法允许您定义装饰器与使用它们分开。它使得可以有两个步骤 `x=decorator_factory(foo)` `@x; def ...` 以及更常见的单步 `@decorator_factory(foo); 定义...`。虽然它不太常见,但它被使用并且非常有用。 (2认同)

srj*_*srj 277

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

@decorator
def foo(*args, **kwargs):
    pass
Run Code Online (Sandbox Code Playgroud)

翻译成

foo = decorator(foo)
Run Code Online (Sandbox Code Playgroud)

所以如果装饰者有参数,

@decorator_with_args(arg)
def foo(*args, **kwargs):
    pass
Run Code Online (Sandbox Code Playgroud)

翻译成

foo = decorator_with_args(arg)(foo)
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

更新:

上面,foo成为real_decorator(foo)

装饰函数的一个效果foo是在装饰器声明时重写名称.foo被任何返回的东西"覆盖" real_decorator.在这种情况下,一个新的功能对象.

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

>>> print(foo)
<function _pseudo_decor.<locals>.ret_fun at 0x10666a2f0>
Run Code Online (Sandbox Code Playgroud)

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):
        #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)>
Run Code Online (Sandbox Code Playgroud)

  • 什么是'arg`在这里!? (3认同)
  • 你的答案完美地解释了装饰者固有的正交性,谢谢 (2认同)

Dac*_*cav 79

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

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

def parametrized(dec):
    def layer(*args, **kwargs):
        def repl(f):
            return dec(f, *args, **kwargs)
        return repl
    return layer
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

随着@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)
Run Code Online (Sandbox Code Playgroud)

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

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

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
Run Code Online (Sandbox Code Playgroud)

最后一点:这里我不是functools.wraps用于包装函数,但我建议一直使用它.

  • 没有完全使用它,但帮助我理解了这个概念:) 谢谢! (3认同)
  • 哦,孩子,我在这一整天都失去了.值得庆幸的是,我来到[这个答案](/sf/answers/111613911/)(顺便提一下,这可能是有史以来在整个互联网上创建的最佳答案).他们也使用你的`@ parametrized'技巧.我遇到的问题是我忘记了`@`语法**等于实际调用**(不知怎的,我知道并且不知道你可以从我的问题收集的同时).因此,如果你想将`@`语法翻译成*世俗的电话*以检查它是如何工作的,你最好先暂时将它评论出来,或者你最终调用它两次并得到mumbojumbo结果 (3认同)

Ros*_*s R 67

这是t.dubrownik答案的略微修改版本.为什么?

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

所以使用@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
Run Code Online (Sandbox Code Playgroud)

  • 我正是这样做的,但是在带有flask的AWS lambdas上它不起作用:python 3.8返回此错误:`AssertionError:视图函数映射正在覆盖现有端点函数:authorization_required_wrapper` (5认同)
  • 这是我最喜欢的答案,因为包装至关重要。 (3认同)

Ros*_*ers 37

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

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

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')
Run Code Online (Sandbox Code Playgroud)

打印:

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
Run Code Online (Sandbox Code Playgroud)

有关详细信息,请参阅Bruce Eckel的文章.

  • 小心装饰类.除非您手动重新发明instancemethod描述符的逻辑,否则它们不适用于方法. (20认同)
  • 德尔南,关心详细说明?我只需要使用一次这种模式,所以我还没有遇到任何陷阱. (9认同)
  • @jamesc这也是,虽然这相对容易解决.我所指的具体情况是`类Foo:@MyDec(...)def方法(self,...):blah`不起作用因为`Foo().method`不是绑定方法并且不会自动通过`self`.这也可以通过使`MyDec`成为描述符并在`__get__`中创建绑定方法来修复,但它更复杂,更不明显.最后,装饰器类并不像它们看起来那么方便. (9认同)
  • @RossRogers我的猜测是@delnan是指`__name__`之类的东西,装饰者类的实例不会有? (2认同)
  • @delnan我希望看到这个警告更突出.我正在努力,并且有兴趣看到一个可以工作的解决方案(虽然可能会涉及更为明显的解决方案). (2认同)

Shi*_*hah 23

编写一个带参数和不带参数的装饰器是一个挑战,因为 Python 期望在这两种情况下有完全不同的行为!许多答案都试图解决这个问题,下面是@norok2 对答案的改进。具体来说,这种变化消除了使用locals()

遵循@norok2 给出的相同示例:

import functools

def multiplying(f_py=None, factor=1):
    assert callable(f_py) or f_py is None
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return factor * func(*args, **kwargs)
        return wrapper
    return _decorator(f_py) if callable(f_py) else _decorator


@multiplying
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying()
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying(factor=10)
def summing(x): return sum(x)

print(summing(range(10)))
# 450
Run Code Online (Sandbox Code Playgroud)

玩这个代码

问题是用户必须提供参数的键值对而不是位置参数,并且第一个参数是保留的。

  • 这真是天才。 (2认同)

Gaj*_*mbi 11

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
Run Code Online (Sandbox Code Playgroud)

装饰器的用法

@decorator(2)
def adder(*args):
    sum=0
    for i in args:
        sum+=i
    return sum
Run Code Online (Sandbox Code Playgroud)

然后

adder(2,3)
Run Code Online (Sandbox Code Playgroud)

产生

10
Run Code Online (Sandbox Code Playgroud)

adder('hi',3)
Run Code Online (Sandbox Code Playgroud)

产生

---------------------------------------------------------------------------
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
Run Code Online (Sandbox Code Playgroud)

  • 在这里的所有帖子中,这个答案对于我理解如何传递和处理参数最有用。 (3认同)

Mil*_*vić 10

在此输入图像描述

  • 在这里,我们使用两个不同的名称和两个不同的年龄运行了两次显示信息。
  • 现在,每次我们运行显示信息时,我们的装饰器还添加了在包装函数之前和之后打印一行的功能。
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)
Run Code Online (Sandbox Code Playgroud)

输出:

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 
Run Code Online (Sandbox Code Playgroud)
  • 现在让我们继续让装饰器函数接受参数。

  • 例如,假设我想要包装器中所有这些打印语句的可自定义前缀。

  • 现在这将是向装饰器争论的一个很好的候选者。

  • 我们传入的参数将是该前缀。现在为了做到这一点,我们只需向装饰器添加另一个外层,因此我将其称为前缀装饰器函数。

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)
Run Code Online (Sandbox Code Playgroud)

输出:

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 
Run Code Online (Sandbox Code Playgroud)
  • 现在,我们在包装函数中的打印语句之前有这个LOG:前缀,您可以随时更改它。


nor*_*ok2 7

这是用于函数装饰器的模板,该模板不需要提供()任何参数即可:

import functools


def decorator(x_or_func=None, *decorator_args, **decorator_kws):
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kws):
            if 'x_or_func' not in locals() \
                    or callable(x_or_func) \
                    or x_or_func is None:
                x = ...  # <-- default `x` value
            else:
                x = x_or_func
            return func(*args, **kws)

        return wrapper

    return _decorator(x_or_func) if callable(x_or_func) else _decorator
Run Code Online (Sandbox Code Playgroud)

下面是一个示例:

def multiplying(factor_or_func=None):
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            if 'factor_or_func' not in locals() \
                    or callable(factor_or_func) \
                    or factor_or_func is None:
                factor = 1
            else:
                factor = factor_or_func
            return factor * func(*args, **kwargs)
        return wrapper
    return _decorator(factor_or_func) if callable(factor_or_func) else _decorator


@multiplying
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying()
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying(10)
def summing(x): return sum(x)

print(summing(range(10)))
# 450
Run Code Online (Sandbox Code Playgroud)

  • 为什么需要签入“locals()”? (2认同)

run*_*ace 7

上面的答案很好。这也说明了@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__)
Run Code Online (Sandbox Code Playgroud)

印刷:

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
Run Code Online (Sandbox Code Playgroud)


Hen*_*l B 5

就这么简单

def real_decorator(any_number_of_arguments):
   def pseudo_decorator(function_to_be_decorated):

       def real_wrapper(function_arguments):
           print(function_arguments)
           result = function_to_be_decorated(any_number_of_arguments)
           return result

       return real_wrapper
   return pseudo_decorator
Run Code Online (Sandbox Code Playgroud)

现在

@real_decorator(any_number_of_arguments)
def some_function(function_arguments):
        return "Any"
Run Code Online (Sandbox Code Playgroud)

  • 请注意,这不会像普通装饰器那样工作,如果“any_number_of_arguments”是可选参数,您仍然需要在装饰器的末尾编写“()”。 (3认同)