【问题标题】:Decorators with parameters?带参数的装饰器?
【发布时间】:2022-12-28 05:32:12
【问题描述】:

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

【问题讨论】:

标签: python function parameters arguments decorator


【解决方案1】:

带参数的装饰器的语法有点不同——带参数的装饰器应该返回一个函数,它将采取一个功能并返回另一个函数。所以它真的应该返回一个普通的装饰器。有点混乱,对吧?我的意思是:

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

Here你可以阅读更多关于这个主题的内容——也可以使用可调用对象来实现它,这也在那里进行了解释。

【讨论】:

  • 我想知道为什么GVR没有通过在'function'之后将参数作为后续装饰器参数传递来实现它。 “哟,老兄,我听说你喜欢闭包……”等等。
  • > 函数是第一个参数还是最后一个?显然首先,因为参数是可变长度的参数列表。 > 同样奇怪的是,您会使用与定义中的签名不同的签名“调用”该函数。正如您所指出的,它实际上非常适合 - 它非常类似于调用类方法的方式。为了更清楚,你可以有类似 decorator(self_func, param1, ...) 约定的东西。但请注意:我不主张在这里进行任何更改,Python 在这方面走得太远了,我们可以看到重大更改是如何产生的……
  • 你忘了装饰包装纸非常有用的 functools.wraps :)
  • 您在调用函数时忘记了返回,即return function(*args, **kwargs)
  • 可能很明显,但以防万一:您需要将此装饰器用作 @decorator() 而不仅仅是 @decorator,即使您只有可选参数。
【解决方案2】:

编辑: 要深入了解装饰器的心智模型,请查看thisawesome Pycon Talk。非常值得 30 分钟。

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

@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 是一个接受自定义参数并返回实际装饰器(将应用于装饰函数)的函数。

我使用了一个简单的部分技巧来简化我的装饰器

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 在装饰器声明时被覆盖。 fooreal_decorator 返回的任何内容“覆盖”。在这种情况下,一个新的函数对象。

foo 的所有元数据都被覆盖,特别是文档字符串和函数名称。

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

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):
        # pre function execution stuff here, for eg.
        print("decorator argument is %s" % str(argument))
        returned_value =  fun(*args, **kwargs)
        # post execution stuff here, for eg.
        print("returned value is %s" % returned_value)
        return returned_value

    return ret_fun

real_decorator1 = partial(_pseudo_decor, argument="some_arg")
real_decorator2 = partial(_pseudo_decor, argument="some_other_arg")

@real_decorator1
def bar(*args, **kwargs):
    pass

>>> print(bar)
<function __main__.bar(*args, **kwargs)>

>>> bar(1,2,3, k="v", x="z")
decorator argument is some_arg
returned value is None

【讨论】:

  • 您的回答完美地解释了装饰器固有的正交性,谢谢
  • @Mr_and_Mrs_D,我用 functool.wraps 的例子更新了帖子。将它添加到示例中可能会使读者更加困惑。
  • 这里的arg是什么!?
  • 您如何将传递给bar 的参数传递给real_decorator 的参数?
  • 那么当您不知道 arg 直到实际运行该功能时,它将如何工作?也就是你想用 HTML 标签包围一个字符串,但每次使用的标签都可以不同(甚至是用户定义的)?
【解决方案3】:

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

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

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

【讨论】:

  • 我正是这样做的,但是在带有烧瓶的 AWS lambdas 上它不起作用:python 3.8 返回此错误:AssertionError: View function mapping is overwriting an existing endpoint function: authorization_required_wrapper
  • 这是我最喜欢的答案,因为包装很重要。
  • 感谢您的回答,我花了一些时间...*围绕*这个概念思考...ba dum tsk ...大声笑。所以对我来说这里的关键概念是它有 3 层深。找到更多相关信息:realpython.com/primer-on-python-decorators/…
【解决方案4】:

我想展示一个非常优雅的想法。 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)

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

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

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 作为包装函数,但我建议始终使用它。

【讨论】:

  • 没有完全使用它,但帮助我了解了这个概念 :) 谢谢!
  • 我试过这个并有一些issues
  • @Jeff 你能和我们分享你遇到的那种问题吗?
  • 我把它链接到我的问题上,我确实弄明白了......我需要在我的特定案例中致电@wraps
  • 天哪,我为此浪费了一整天的时间。值得庆幸的是,我找到了this answer(顺便说一下,这可能是整个互联网上有史以来最好的答案)。他们也使用你的@parametrized 把戏。我遇到的问题是我忘记了 @ 语法等于实际调用(不知怎的,我知道这一点,但同时不知道这一点,因为你可以从我的问题中收集到)。所以如果你想将@语法翻译成平凡的电话要检查它是如何工作的,你最好先暂时把它注释掉,否则你最终会调用它两次并得到胡言乱语的结果
【解决方案5】:

我认为您的问题是将参数传递给您的装饰器。这有点棘手而且不简单。

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

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.

【讨论】:

  • 当心装饰类。它们对方法不起作用,除非您手动重新发明实例方法描述符的逻辑。
  • delnan,想详细说明一下吗?我只用过一次这种模式,所以我还没有遇到任何陷阱。
  • @RossRogers 我的猜测是@delnan 指的是像__name__ 这样的东西,装饰器类的实例不会有?
  • @jamesc 那也是,尽管这相对容易解决。我指的具体情况是class Foo: @MyDec(...) def method(self, ...): blah,它不起作用,因为Foo().method 不会是绑定方法,也不会自动传递self。这也可以通过将 MyDec 设为描述符并在 __get__ 中创建绑定方法来解决,但它涉及更多且不那么明显。最后,装饰器类并不像看起来那么方便。
  • @delnan 我希望看到这个警告更加突出。我正在努力,并且有兴趣看到一个确实有效的解决方案(虽然可能涉及更多但不太明显)。
【解决方案6】:

编写带参数和不带参数的装饰器是一个挑战,因为 Python 期望在这两种情况下有完全不同的行为!许多答案都试图解决这个问题,下面是对answer by @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

Play with this code

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

【讨论】:

  • 这真是个天才。
  • 请详细说明“Python 在这两种情况下期望完全不同的行为”
  • 将乘法函数的返回值从 return _decorator(f_py) if callable(f_py) else _decorator 更改为 return _decorator(f_py) if f_py else _decoratorreturn _decorator if f_py is None else _decorator(f_py) 怎么样,因为您已经断言它是可调用的或无。这应该比第二次调用callable“更有效率”。
  • 这就是我一直在寻找的模式 - 一个可以使用和不使用 args 的装饰器。我有经验的例子是 Click(来自 Flask 项目的命令生成器)和 Dramatiq(Celery 的任务队列替代品)。我特别推荐查看 Dramatiq 的实现:github.com/Bogdanp/dramatiq/blob/master/dramatiq/actor.py
【解决方案7】:
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

装饰器的使用

@decorator(2)
def adder(*args):
    sum=0
    for i in args:
        sum+=i
    return sum

然后

adder(2,3)

产生

10

adder('hi',3)

产生

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

【讨论】:

  • 在这里的所有帖子中,这个答案对我理解如何传递和处理参数最有用。
【解决方案8】:

这是函数装饰器的模板,如果不提供任何参数则不需要 () 并且支持位置参数和关键字参数(但需要检查 locals() 以确定第一个参数是否是要装饰的函数或不):

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

下面给出了一个例子:

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

或者,如果不需要位置参数,则可以放宽检查 wrapper() 中第一个参数的需要(从而消除使用 locals() 的需要):

import functools


def decorator(func_=None, **decorator_kws):
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kws):
            return func(*args, **kws)
        return wrapper

    if callable(func_):
        return _decorator(func_)
    elif func_ is None:
        return _decorator
    else:
        raise RuntimeWarning("Positional arguments are not supported.")

下面给出了一个例子:

import functools


def multiplying(func_=None, factor=1):
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return factor * func(*args, **kwargs)
        return wrapper

    if callable(func_):
        return _decorator(func_)
    elif func_ is None:
        return _decorator
    else:
        raise RuntimeWarning("Positional arguments are not supported.")


@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


@multiplying(10)
def summing(x): return sum(x)
print(summing(range(10)))
# RuntimeWarning Traceback (most recent call last)
#    ....
# RuntimeWarning: Positional arguments are not supported.

(部分修改自@ShitalShah's answer

【讨论】:

  • 另请注意 factor_or_func(或任何其他参数)should never gets reassignedwrapper() 中。
  • 为什么需要打卡locals()
  • @ShitalShah 涵盖了在没有 () 的情况下使用装饰器的情况。
【解决方案9】:

就这么简单

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

现在

@real_decorator(any_number_of_arguments)
def some_function(function_arguments):
        return "Any"

【讨论】:

  • 请注意,这与普通装饰器的工作方式不同,如果 any_number_of_arguments 是可选参数,您仍然必须在装饰器的末尾写上 ()
【解决方案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__, '
')
        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)

输出:

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 
  • 所以现在让我们继续让我们的装饰函数接受参数。

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

  • 现在这将是装饰器参数的一个很好的候选者。

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

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__, '
')
            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)

输出:

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 
  • 现在我们在包装函数中的 print 语句之前有了 LOG: 前缀,您可以随时更改它。

【讨论】:

    【解决方案11】:

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

    印刷:

    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
    

    【讨论】:

      【解决方案12】:

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

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

      【讨论】:

      • 这行得通。虽然是的,但这使得很难将值设置为装饰器。
      【解决方案13】:

      众所周知,以下两段代码几乎是等价的:

      @dec
      def foo():
          pass    foo = dec(foo)
      
      ############################################
      foo = dec(foo)
      

      一个常见的错误是认为 @ 只是隐藏了最左边的参数。

      @dec(1, 2, 3)
      def foo():
          pass    
      ###########################################
      foo = dec(foo, 1, 2, 3)
      

      如果以上是 @ 的工作方式,那么编写装饰器会容易得多。不幸的是,这不是做事的方式。


      考虑一个装饰器Waitwhich haults 程序执行几秒钟。 如果你没有通过等待时间 那么默认值为 1 秒。 用例如下所示。

      ##################################################
      @Wait
      def print_something(something):
          print(something)
      
      ##################################################
      @Wait(3)
      def print_something_else(something_else):
          print(something_else)
      
      ##################################################
      @Wait(delay=3)
      def print_something_else(something_else):
          print(something_else)
      

      Wait有参数时,比如@Wait(3),那么调用Wait(3) 被执行发生任何其他事情。

      即下面两段代码是等价的

      @Wait(3)
      def print_something_else(something_else):
          print(something_else)
      
      ###############################################
      return_value = Wait(3)
      @return_value
      def print_something_else(something_else):
          print(something_else)
      

      这是个问题。

      if `Wait` has no arguments:
          `Wait` is the decorator.
      else: # `Wait` receives arguments
          `Wait` is not the decorator itself.
          Instead, `Wait` ***returns*** the decorator
      

      一种解决方案如下所示:

      让我们从创建以下类开始,DelayedDecorator

      class DelayedDecorator:
          def __init__(i, cls, *args, **kwargs):
              print("Delayed Decorator __init__", cls, args, kwargs)
              i._cls = cls
              i._args = args
              i._kwargs = kwargs
          def __call__(i, func):
              print("Delayed Decorator __call__", func)
              if not (callable(func)):
                  import io
                  with io.StringIO() as ss:
                      print(
                          "If only one input, input must be callable",
                          "Instead, received:",
                          repr(func),
                          sep="
      ",
                          file=ss
                      )
                      msg = ss.getvalue()
                  raise TypeError(msg)
              return i._cls(func, *i._args, **i._kwargs)
      

      现在我们可以这样写:

       dec = DelayedDecorator(Wait, delay=4)
       @dec
       def delayed_print(something):
          print(something)
      

      注意:

      • dec 不接受多个参数。
      • dec 只接受要包装的函数。

        进口检验 PolyArgDecoratorMeta 类(类型): 定义称呼(等等,*args,**kwargs): 尝试: arg_count = len(args) 如果(arg_count == 1): 如果可调用(args [0]): 超类 = inspect.getmro(PolyArgDecoratorMeta)[1] r = 超类。称呼(等等,args[0]) 别的: r = DelayedDecorator(等待,*args,**kwargs) 别的: r = DelayedDecorator(等待,*args,**kwargs) 最后: 经过 返回

        导入时间 等待类(metaclass=PolyArgDecoratorMeta): 定义在里面(i, func, delay = 2): i._func = 函数 i._delay = 延迟

        def __call__(i, *args, **kwargs):
            time.sleep(i._delay)
            r = i._func(*args, **kwargs)
            return r 
        

      下面两段代码是等价的:

      @Wait
      def print_something(something):
           print (something)
      
      ##################################################
      
      def print_something(something):
          print(something)
      print_something = Wait(print_something)
      

      我们可以很慢的打印"something"到控制台,如下:

      print_something("something")
      
      #################################################
      @Wait(delay=1)
      def print_something_else(something_else):
          print(something_else)
      
      ##################################################
      def print_something_else(something_else):
          print(something_else)
      
      dd = DelayedDecorator(Wait, delay=1)
      print_something_else = dd(print_something_else)
      
      ##################################################
      
      print_something_else("something")
      

      最后的笔记

      它可能看起来像很多代码,但您不必每次都编写类DelayedDecoratorPolyArgDecoratorMeta。您必须亲自编写的唯一代码如下,相当短:

      from PolyArgDecoratorMeta import PolyArgDecoratorMeta
      import time
      class Wait(metaclass=PolyArgDecoratorMeta):
       def __init__(i, func, delay = 2):
           i._func = func
           i._delay = delay
      
       def __call__(i, *args, **kwargs):
           time.sleep(i._delay)
           r = i._func(*args, **kwargs)
           return r
      

      【讨论】:

        【解决方案14】:

        它是一个可以通过多种方式调用的装饰器(在python3.7中测试):

        import functools
        
        
        def my_decorator(*args_or_func, **decorator_kwargs):
        
            def _decorator(func):
        
                @functools.wraps(func)
                def wrapper(*args, **kwargs):
        
                    if not args_or_func or callable(args_or_func[0]):
                        # Here you can set default values for positional arguments
                        decorator_args = ()
                    else:
                        decorator_args = args_or_func
        
                    print(
                        "Available inside the wrapper:",
                        decorator_args, decorator_kwargs
                    )
        
                    # ...
                    result = func(*args, **kwargs)
                    # ...
        
                    return result
        
                return wrapper
        
            return _decorator(args_or_func[0]) 
                if args_or_func and callable(args_or_func[0]) else _decorator
        
        
        @my_decorator
        def func_1(arg): print(arg)
        
        func_1("test")
        # Available inside the wrapper: () {}
        # test
        
        
        @my_decorator()
        def func_2(arg): print(arg)
        
        func_2("test")
        # Available inside the wrapper: () {}
        # test
        
        
        @my_decorator("any arg")
        def func_3(arg): print(arg)
        
        func_3("test")
        # Available inside the wrapper: ('any arg',) {}
        # test
        
        
        @my_decorator("arg_1", 2, [3, 4, 5], kwarg_1=1, kwarg_2="2")
        def func_4(arg): print(arg)
        
        func_4("test")
        # Available inside the wrapper: ('arg_1', 2, [3, 4, 5]) {'kwarg_1': 1, 'kwarg_2': '2'}
        # test
        
        

        PS感谢用户@norok2- https://stackoverflow.com/a/57268935/5353484

        更新程序用于根据注释验证类的函数和方法的参数和/或结果的装饰器。可同步或异步版本使用:https://github.com/EvgeniyBurdin/valdec

        【讨论】:

          【解决方案15】:

          这是一个使用带参数的装饰器的 Flask 示例。假设我们有一个路由 '/user/name' 并且我们想要映射到他的主页。

          def matchR(dirPath):
              def decorator(func):
                  def wrapper(msg):
                      if dirPath[0:6] == '/user/':
                          print(f"User route '{dirPath}' match, calling func {func}")
                          name = dirPath[6:]
                          return func(msg2=name, msg3=msg)
                      else:
                          print(f"Input dirPath '{dirPath}' does not match route '/user/'")
                          return
                  return  wrapper
              return decorator
          
          #@matchR('/Morgan_Hills')
          @matchR('/user/Morgan_Hills')
          def home(**kwMsgs):
              for arg in kwMsgs:
                  if arg == 'msg2':
                      print(f"In home({arg}): Hello {kwMsgs[arg]}, welcome home!")
                  if arg == 'msg3':
                      print(f"In home({arg}): {kwMsgs[arg]}")
          
          home('This is your profile rendered as in index.html.')
          

          输出:

          User route '/user/Morgan_Hills' match, calling func <function home at 0x000001DD5FDCD310>
          In home(msg2): Hello Morgan_Hills, welcome home!
          In home(msg3): This is your profile rendered as in index.html.
          

          【讨论】:

            【解决方案16】:

            这是柯里化函数的一个很好的用例。

            Curried 函数本质上会延迟调用函数,直到所有输入都已提供。

            这可以用于包装器或函数式编程等各种用途。在这种情况下,让我们创建一个接收输入的包装器。

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

            安装熊猫:

            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!
            

            【讨论】:

              【解决方案17】:

              带有参数的装饰器应该返回一个函数,该函数将接受一个函数并返回另一个你可以做到的函数

              def decorator_factory(argument):
                  def decorator(function):
                      def wrapper(*args, **kwargs):
                          """
                              add somhting
                          """
                          return  function(*args, **kwargs)
                      return wrapper
                  return decorator
              

              或者您可以使用 functools 模块中的部分

              def decorator(function =None,*,argument ):
                      if function is None :
                          return partial(decorator,argument=argument)
                      def wrapper(*args, **kwargs):
                          """
                              add somhting
                          """
                          return  function(*args, **kwargs)
                      return wrapper
              

              在第二个选项中,只需确保传递这样的参数:

              @decorator(argument = 'args')
              def func():
                  pass
              

              【讨论】:

                【解决方案18】:

                定义此“decoratorize 函数”以生成定制的装饰器函数:

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

                以这种方式使用它:

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

                【讨论】:

                  【解决方案19】:

                  我想一个工作,真实世界的例子,使用示例最通用的用例在这里可能很有价值。


                  下面是函数的装饰器,它在进入和退出函数时打印日志。

                  参数控制天气是否打印输入输出值,日志级别等。

                  import logging 
                  from functools import wraps
                  
                  
                  def log_in_out(logger=logging.get_logger(), is_print_input=True, is_print_output=True, is_method=True, log_level=logging.DEBUG):
                      """
                      @param logger-
                      @param is_print_input- toggle printing input arguments
                      @param is_print_output- toggle printing output values
                      @param is_method- True for methods, False for functions. Makes "self" not printed in case of is_print_input==True
                      @param log_level-
                  
                      @returns- a decorator that logs to logger when entering or exiting the decorated function.
                      Don't uglify your code!
                      """
                  
                      def decor(fn):
                          @wraps(fn)
                          def wrapper(*args, **kwargs):
                              if is_print_input:
                                  logger.log(
                                      msg=f"Entered {fn.__name__} with args={args[1:] if is_method else args}, kwargs={kwargs}",
                                      level=log_level
                                  )
                              else:
                                  logger.log(
                                      msg=f"Entered {fn.__name__}",
                                      level=log_level
                                  )
                  
                              result = fn(*args, **kwargs)
                  
                              if is_print_output and result is not None:
                                  logger.log(
                                      msg=f"Exited {fn.__name__} with result {result}",
                                      level=log_level,
                                  )
                              else:
                                  logger.log(
                                      msg=f"Exited {fn.__name__}",
                                      level=log_level
                                  )
                  
                              return result
                  
                          return wrapper
                  
                      return decor
                  
                  

                  用法:

                   @log_in_out(is_method=False, is_print_input=False)
                      def foo(a, b=5):
                          return 3, a
                  

                  foo(2) --> 打印

                  输入 foo
                  退出 foo 结果为 (3, 2)

                      class A():
                          @log_in_out(is_print_output=False)
                          def bar(self, c, m, y):
                              return c, 6
                  

                  a = A() a.bar(1, 2, y=3) --> 打印

                  输入 bar with args=(1, 2), kwargs={y:3}
                  退出酒吧

                  【讨论】:

                    【解决方案20】:

                    假设你有一个函数

                    def f(*args):
                        print(*args)
                    

                    你想添加一个接受参数的装饰器,如下所示:

                    @decorator(msg='hello')
                    def f(*args):
                        print(*args)
                    

                    这意味着 Python 将修改 f 如下:

                    f = decorator(msg='hello')(f)
                    

                    因此,decorator(msg='hello') 部分的返回应该是一个包装函数,它接受函数 f 并返回修改后的函数。然后你可以执行修改后的功能。

                    def decorator(**kwargs):
                        def wrap(f):
                            def modified_f(*args):
                                print(kwargs['msg']) # use passed arguments to the decorator
                                return f(*args)
                            return modified_f
                        return wrap
                    

                    所以,当你打电话给f时,就像你在做的那样: decorator(msg='hello')(f)(args) === wrap(f)(args) === modified_f(args) 但是 modified_f 可以访问传递给装饰器的 kwargs

                    的输出

                    f(1,2,3)
                    

                    将:

                    hello
                    (1, 2, 3)
                    

                    【讨论】:

                      【解决方案21】:

                      例如,我创建了multiply()下面可以接受一个或没有参数或没有来自装饰器的括号,我在下面创建了sum()

                      from numbers import Number
                      
                      def multiply(num=1):
                          def _multiply(func):
                              def core(*args, **kwargs):
                                  result = func(*args, **kwargs)
                                  if isinstance(num, Number):
                                      return result * num
                                  else:
                                      return result
                              return core
                          if callable(num):
                              return _multiply(num)
                          else:
                              return _multiply
                      
                      def sum(num1, num2):
                          return num1 + num2
                      

                      现在,我把@multiply(5)放在sum()上,然后调用sum(4, 6),如下所示:

                      # (4 + 6) x 5 = 50
                      
                      @multiply(5) # Here
                      def sum(num1, num2):
                          return num1 + num2
                      
                      result = sum(4, 6)
                      print(result)
                      

                      然后,我可以得到以下结果:

                      50
                      

                      接下来,我将@multiply()放在sum()上,然后调用sum(4, 6),如下所示:

                      # (4 + 6) x 1 = 10
                      
                      @multiply() # Here
                      def sum(num1, num2):
                          return num1 + num2
                          
                      result = sum(4, 6)
                      print(result)
                      

                      或者,我把@multiply放在sum()上,然后调用sum(4, 6),如下所示:

                      # 4 + 6 = 10
                      
                      @multiply # Here
                      def sum(num1, num2):
                          return num1 + num2
                          
                      result = sum(4, 6)
                      print(result)
                      

                      然后,我可以得到以下结果:

                      10
                      

                      【讨论】:

                        【解决方案22】:

                        如果函数和装饰器都必须采用参数,您可以采用以下方法。

                        例如,有一个名为 decorator1 的装饰器,它接受一个参数

                        @decorator1(5)
                        def func1(arg1, arg2):
                            print (arg1, arg2)
                        
                        func1(1, 2)
                        

                        现在,如果 decorator1 参数必须是动态的,或者在调用函数时传递,

                        def func1(arg1, arg2):
                            print (arg1, arg2)
                        
                        
                        a = 1
                        b = 2
                        seconds = 10
                        
                        decorator1(seconds)(func1)(a, b)
                        

                        在上面的代码中

                        • secondsdecorator1 的参数
                        • a, bfunc1 的参数

                        【讨论】:

                          【解决方案23】:

                          带有参数的装饰匿名的环境。

                          在众多可能性中,提出了“嵌套”句法糖修饰的两种变体。它们在目标函数的执行顺序上彼此不同,它们的效果通常是独立的(非交互)。

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

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

                          语法糖修饰@first_internal(send_msg)('...end')要求版本>=3.9,见PEP 614Relaxing Grammar Restrictions On Decorators。

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

                          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
                          

                          评论

                          • 作品装饰器,例如后拉和前推(也许在更计算机科学的术语中:co-和 resp.contra-variant decorator)可能更有用但需要特别注意,例如组合规则,检查哪些参数去哪里等等

                          • 语法糖充当目标函数的一种partial:一旦装饰就没有办法返回(没有额外的导入)但它不是强制性的,装饰器也可以在它的延长形式,即first_external(send_msg)("start...")(test_function)(2)

                          • timeit.repeat(..., repeat=5, number=10000) 工作台的结果比较了经典的deflambda 装饰,显示它们几乎是等价的:

                            • 对于lambda[6.200810984999862, 6.035239247000391, 5.346362481000142, 5.987880147000396, 5.5331550319997405] - 意思是 -> 5.8206

                            • 对于def[6.165001932999985, 5.554595884999799, 5.798066574999666, 5.678178028000275, 5.446507932999793] - 意思是 -> 5.7284

                          • 自然地,非匿名对应方是可能的,并且提供了更大的灵活性

                          【讨论】:

                          • 表明函数可以重写为 lambda 并没有给解决方案增加任何有趣的东西;它附带使装饰器接受参数所需的重要技术。
                          • @Karl Knechtel 没有人提到lambda 方法,我觉得它很好奇。此外,我表明语法糖支持多次调用,并且可以通过让参数再次成为函数来收集抽象
                          • 因为“lambda 方法”是基本上没有不同的方法.
                          猜你喜欢
                          • 2018-01-07
                          • 2014-07-21
                          • 1970-01-01
                          • 2014-10-14
                          • 2012-03-03
                          • 2014-03-24
                          • 2015-10-19
                          相关资源
                          最近更新 更多