带参数的装饰器?

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认同)