带参数的装饰器?

发布于 2024-11-05 23:07:49 字数 740 浏览 1 评论 0原文

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

I have a problem with the transfer of the variable insurance_mode by the decorator. I would do it by the following decorator statement:

@execute_complete_reservation(True)
def test_booking_gta_object(self):
    self.test_select_gta_object()

but unfortunately, this statement does not work. Perhaps maybe there is better way to solve this problem.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(24

乖乖公主 2024-11-12 23:07:49

带参数的装饰器的语法有点不同 - 带参数的装饰器应该返回一个函数,该函数将接受一个函数并返回另一个函数。所以它应该返回一个正常的装饰器。有点令人困惑,对吧?我的意思是:

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

这里 您可以阅读有关该主题的更多信息 - 也可以使用可调用对象来实现这一点,并且那里也对此进行了解释。

The syntax for decorators with arguments is a bit different - the decorator with arguments should return a function that will take a function and return another function. So it should really return a normal decorator. A bit confusing, right? What I mean is:

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 you can read more on the subject - it's also possible to implement this using callable objects and that is also explained there.

只怪假的太真实 2024-11-12 23:07:49

编辑:要深入了解装饰器的心智模型,请查看此 很棒的 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

Edit : for an in-depth understanding of the mental model of decorators, take a look at this awesome Pycon Talk. well worth the 30 minutes.

One way of thinking about decorators with arguments is

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

translates to

foo = decorator(foo)

So if the decorator had arguments,

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

translates to

foo = decorator_with_args(arg)(foo)

decorator_with_args is a function which accepts a custom argument and which returns the actual decorator (that will be applied to the decorated function).

I use a simple trick with partials to make my decorators easy

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

Update:

Above, foo becomes real_decorator(foo)

One effect of decorating a function is that the name foo is overridden upon decorator declaration. foo is "overridden" by whatever is returned by real_decorator. In this case, a new function object.

All of foo's metadata is overridden, notably docstring and function name.

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

functools.wraps gives us a convenient method to "lift" the docstring and name to the returned function.

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
宫墨修音 2024-11-12 23:07:49

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

  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

Here is a slightly modified version of t.dubrownik's answer. Why?

  1. As a general template, you should return the return value from the original function.
  2. This changes the name of the function, which could affect other decorators / code.

So use @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
匿名。 2024-11-12 23:07:49

我想展示一个非常优雅的想法。 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 来作为包装器函数,但我建议始终使用它。

I'd like to show an idea which is IMHO quite elegant. The solution proposed by t.dubrownik shows a pattern which is always the same: you need the three-layered wrapper regardless of what the decorator does.

So I thought this is a job for a meta-decorator, that is, a decorator for decorators. As a decorator is a function, it actually works as a regular decorator with arguments:

def parametrized(dec):
    def layer(*args, **kwargs):
        def repl(f):
            return dec(f, *args, **kwargs)
        return repl
    return layer

This can be applied to a regular decorator in order to add parameters. So for instance, say we have the decorator which doubles the result of a function:

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)

With @parametrized we can build a generic @multiply decorator having a parameter

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

Conventionally the first parameter of a parametrized decorator is the function, while the remaining arguments will correspond to the parameter of the parametrized decorator.

An interesting usage example could be a type-safe assertive decorator:

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

A final note: here I'm not using functools.wraps for the wrapper functions, but I would recommend using it all the times.

浅笑依然 2024-11-12 23:07:49

编写一个可以使用参数和不使用参数的装饰器是一个挑战,因为 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

使用此代码

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

Writing a decorator that works with and without parameter is a challenge because Python expects completely different behavior in these two cases! Many answers have tried to work around this and below is an improvement of answer by @norok2. Specifically, this variation eliminates the use of locals().

Following the same example as given by @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.

The catch is that the user must supply key,value pairs of parameters instead of positional parameters and the first parameter is reserved.

梦在深巷 2024-11-12 23:07:49

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

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

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

请参阅 Bruce Eckel 的文章了解更多详情。

I presume your problem is passing arguments to your decorator. This is a little tricky and not straightforward.

Here's an example of how to do this:

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

Prints:

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.

鱼忆七猫命九 2024-11-12 23:07:49
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)

Produces

10

但是

adder('hi',3)

Produces

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

Usage of the decorator

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

Then the

adder(2,3)

produces

10

but

adder('hi',3)

produces

---------------------------------------------------------------------------
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
时光无声 2024-11-12 23:07:49

这是一个函数装饰器的模板,如果没有给出参数,则不需要 () 并且支持位置参数和关键字参数(但需要检查 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 的回答)

This is a template for a function decorator that does not require () if no parameters are to be given and supports both positional and keyword parameters (but requires cheching on locals() to find out if the first parameter is the function to be decorated or not):

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

an example of this is given below:

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

Alternatively, if one does not need positional arguments, one can relax the need for checking on the first parameter within the wrapper() (thus removing the need to use 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.")

an example of this is given below:

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.

(partially reworked from @ShitalShah's answer)

流星番茄 2024-11-12 23:07:49

就这么简单

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"

Simple as this

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

Now

@real_decorator(any_number_of_arguments)
def some_function(function_arguments):
        return "Any"
岁月流歌 2024-11-12 23:07:49

输入图像描述这里

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

输出:

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

输出:

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

enter image description here

  • Here we ran display info twice with two different names and two different ages.
  • Now every time we ran display info, our decorators also added the functionality of printing out a line before and a line after that wrapped function.
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)

output:

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 
  • So now let's go ahead and get our decorator function to accept arguments.

  • For example let's say that I wanted a customizable prefix to all of these print statements within the wrapper.

  • Now this would be a good candidate for an argument to the decorator.

  • The argument that we pass in will be that prefix. Now in order to do, this we're just going to add another outer layer to our decorator, so I'm going to call this a function a prefix decorator.

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)

output:

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 
  • Now we have that LOG: prefix before our print statements in our wrapper function and you can change this any time that you want.
坚持沉默 2024-11-12 23:07:49

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

Great answers above. This one also illustrates @wraps, which takes the doc string and function name from the original function and applies it to the new wrapped version:

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

Prints:

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
甜柠檬 2024-11-12 23:07:49

例如,我在下面创建了 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() on sum(),然后调用 sum(4, 6) 如下所示:

# (4 + 6) x 1 = 10

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

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

# 4 + 6 = 10

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

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

10

For example, I created multiply() below which can accept one or no argument or no parentheses from the decorator and I created sum() below:

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

Now, I put @multiply(5) on sum(), then called sum(4, 6) as shown below:

# (4 + 6) x 5 = 50

@multiply(5) # Here
def sum(num1, num2):
    return num1 + num2

result = sum(4, 6)
print(result)

Then, I could get the result below:

50

Next, I put @multiply() on sum(), then called sum(4, 6) as shown below:

# (4 + 6) x 1 = 10

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

Or, I put @multiply on sum(), then called sum(4, 6) as shown below:

# 4 + 6 = 10

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

Then, I could get the result below:

10
╭⌒浅淡时光〆 2024-11-12 23:07:49

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

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

In my instance, I decided to solve this via a one-line lambda to create a new decorator function:

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

When executed, this prints:

Finished!
All Done!

Perhaps not as extensible as other solutions, but worked for me.

尾戒 2024-11-12 23:07:49

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

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

############################################
foo = dec(foo)

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

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

如果上述是 @ 的工作方式,那么编写装饰器会容易得多。不幸的是,事情并非如此。


考虑一个装饰器Wait,它拖拽
程序执行几秒钟。
如果您没有在等待时间内通过
那么默认值为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="\n",
                    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(类型):
    def call(等等,*args,**kwargs):
    尝试:
    arg_count = len(参数)
    如果(arg_count == 1):
    如果可调用(参数[0]):
    SuperClass =spect.getmro(PolyArgDecoratorMeta)[1]
    r = SuperClass.call(等等,args[0])
    别的:
    r = DelayedDecorator(等待,*args,**kwargs)
    别的:
    r = DelayedDecorator(等待,*args,**kwargs)
    最后:
    经过
    返回r

    导入时间
    等待类(元类=PolyArgDecoratorMeta):
    def init(i, func, 延迟 = 2):
    i._func = 函数
    i._delay = 延迟

    def __call__(i, *args, **kwargs):
        时间.睡眠(i._delay)
        r = i._func(*args, **kwargs)
        返回 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

It is well known that the following two pieces of code are nearly equivalent:

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

############################################
foo = dec(foo)

A common mistake is to think that @ simply hides the leftmost argument.

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

It would be much easier to write decorators if the above is how @ worked. Unfortunately, that’s not the way things are done.


Consider a decorator Waitwhich haults
program execution for a few seconds.
If you don't pass in a Wait-time
then the default value is 1 seconds.
Use-cases are shown below.

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

When Wait has an argument, such as @Wait(3), then the call Wait(3)
is executed before anything else happens.

That is, the following two pieces of code are equivalent

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

This is a problem.

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

One solution is shown below:

Let us begin by creating the following class, 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="\n",
                    file=ss
                )
                msg = ss.getvalue()
            raise TypeError(msg)
        return i._cls(func, *i._args, **i._kwargs)

Now we can write things like:

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

Note that:

  • dec does not not accept multiple arguments.
  • dec only accepts the function to be wrapped.

    import inspect
    class PolyArgDecoratorMeta(type):
    def call(Wait, *args, **kwargs):
    try:
    arg_count = len(args)
    if (arg_count == 1):
    if callable(args[0]):
    SuperClass = inspect.getmro(PolyArgDecoratorMeta)[1]
    r = SuperClass.call(Wait, args[0])
    else:
    r = DelayedDecorator(Wait, *args, **kwargs)
    else:
    r = DelayedDecorator(Wait, *args, **kwargs)
    finally:
    pass
    return r

    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 
    

The following two pieces of code are equivalent:

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

##################################################

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

We can print "something" to the console very slowly, as follows:

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

Final Notes

It may look like a lot of code, but you don't have to write the classes DelayedDecorator and PolyArgDecoratorMeta every-time. The only code you have to personally write something like as follows, which is fairly short:

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
濫情▎り 2024-11-12 23:07:49

带参数的装饰器应该返回一个函数,该函数将接受一个函数并返回另一个函数,您可以这样做,

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

the decorator with arguments should return a function that will take a function and return another function you can do that

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

or you can use partial from functools module

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

in the second option just make sure you pass the arguments like this :

@decorator(argument = 'args')
def func():
    pass
欲拥i 2024-11-12 23:07:49

它是一个可以通过多种方式调用的装饰器(在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

UPD 用于验证参数和/或结果的装饰器类的函数和方法与注释的比较。可用于同步或异步版本:https://github.com/EvgeniyBurdin/valdec

It is a decorator that can be called in a variety of ways (tested in 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 thanks to user @norok2 - https://stackoverflow.com/a/57268935/5353484

UPD Decorator for validating arguments and/or result of functions and methods of a class against annotations. Can be used in synchronous or asynchronous version: https://github.com/EvgeniyBurdin/valdec

—━☆沉默づ 2024-11-12 23:07:49

假设你有一个函数

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 时,就像你在做的那样:
装饰器(msg='hello')(f)(args)
=== wrap(f)(args) === modified_f(args)
但是 modified_f 可以访问传递给装饰器的 kwargs

的输出

f(1,2,3)

将是:

hello
(1, 2, 3)

Suppose you have a function

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

And you want to add a decorator that accepts arguments to it like this:

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

This means Python will modify f as follows:

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

So, the return of the part decorator(msg='hello') should be a wrapper function that accepts the function f and returns the modified function. then you can execute the modified function.

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

So, when you call f, it is like you are doing:
decorator(msg='hello')(f)(args)
=== wrap(f)(args) === modified_f(args)
but modified_f has access to kwargs passed to the decorator

The output of

f(1,2,3)

will be:

hello
(1, 2, 3)
心是晴朗的。 2024-11-12 23:07:49

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

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

这样使用它:

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

define this "decoratorize function" to generate customized decorator function:

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

use it this way:

    @decoratorize(FUN, arg1 = , arg2 = , ...)
    def bar(...):
        ...
谜兔 2024-11-12 23:07:49

这是一个使用带有参数的装饰器的 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.

Here is a Flask example using decorators with parameters. Suppose we have a route '/user/name' and we want to map to his home page.

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.')

Output:

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.
成熟的代价 2024-11-12 23:07:49

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

柯里化函数本质上会延迟函数的调用,直到提供所有输入为止。

这可用于多种用途,例如包装器或函数式编程。在本例中,让我们创建一个接受输入的包装器。

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

安装 Pamda:

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!

This is a great use case for a curried function.

Curried functions essentially delay a function from being called until all inputs have been supplied.

This can be used for a variety of things like wrappers or functional programming. In this case lets create a wrapper that takes in inputs.

I will use a simple package pamda that includes a curry function for python. This can be used as a wrapper for other functions.

Install Pamda:

pip install pamda

Create a simple curried decorator function with two inputs:

@pamda.curry()
def my_decorator(input, func):
    print ("Executing Decorator")
    print(f"input:{input}")
    return func

Apply your decorator with the first input supplied to your target function:

@my_decorator('Hi!')
def foo(input):
    print('Executing Foo!')
    print(f"input:{input}")

Execute your wrapped function:

x=foo('Bye!')

Putting everything together:

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!')

Would give:

Executing Decorator
input:Hi!
Executing Foo!
input:Bye!
十年不长 2024-11-12 23:07:49

我认为一个有效的现实世界示例以及最通用用例的使用示例在这里可能很有价值。


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

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

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) -->印刷

使用 args=(1, 2)、kwargs={y:3} 输入栏
退出栏

I think a working, real-world example, with usage examples of the most generic use-case can be valuable here.


The following is a decorator for functions, which prints to log upon entering and exiting the function.

Parameters control weather or not to print input and output values, log level and so on.

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

usage:

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

foo(2) --> prints

Entered foo
Exited foo with result (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) --> prints

Entered bar with args=(1, 2), kwargs={y:3}
Exited bar

落叶缤纷 2024-11-12 23:07:49

值得检查 使用可选参数制作装饰器 及其副本以获得更全面的答案。

带有注释和测试的通用装饰器骨架代码,试​​图总结所有可能的用例:

from functools import wraps


def my_decorator(*args_or_func, **decorator_kwargs):
    """Originally from:
     https://stackoverflow.com/questions/5929107/decorators-with-parameters/65512042#65512042
    with hints from:
     https://stackoverflow.com/questions/3888158/making-decorators-with-optional-arguments/24617244#24617244"""
    def _decorator(func):
        @wraps(func)  # See: https://docs.python.org/3/library/functools.html#functools.wraps
        def wrapper(*args, **kwargs):
            # Do things before executing the function
            print("Available inside the wrapper:", decorator_args, decorator_kwargs)

            # Execute the original function with its args
            result = func(*args, **kwargs)

            # Do more things after executing the function

            return result

        return wrapper

    # TODO If you need only @my_decorator() or @my_decorator(...) use the else branch only
    # TODO To mitigate TypeError on single callable positional argument allow only keyword arguments:
    #  def my_decorator(func=None, *, decorator_kwarg1='value1', decorator_kwarg2='value2'):  # No **kwargs possible!
    #  and below: if func is not None: _decorator(func) else: _decorator
    # To allow @my_decorator (i.e. without parentheses) WARNING: Use keyword argument for single callable parameter!
    if len(args_or_func) == 1 and len(decorator_kwargs) == 0 and callable(args_or_func[0]):
        # Here you can set default values for positional arguments
        decorator_args = ()
        return _decorator(args_or_func[0])
    else:
        decorator_args = args_or_func  # This "global" variable is used inside _decorator() which is defined above
        return _decorator              # Hint: The function's implementation is evaluated when the function executed


@my_decorator
def func_1(arg): print(arg)


func_1("test1")
# Available inside the wrapper: () {}
# test1


@my_decorator()
def func_2(arg): print(arg)


func_2("test2")
# Available inside the wrapper: () {}
# test2


# Single callable positional argument. BAD INVOCATION! -> TypeError
@my_decorator(lambda x: 42)
def func_3(arg): print(arg)


try:
    func_3("Single callable positional argument. BAD INVOCATION! -> TypeError")
except TypeError as e:
    print("test3")
    print(f"Catched TypeError: TypeError: {e}")
# test3
# Catched TypeError: TypeError: 'int' object is not callable


@my_decorator(lambda x: 42, "any arg")
def func_4(arg): print(arg)


func_4("test4")
# Available inside the wrapper: (<function <lambda> at 0x7f96d94ed870>, 'any arg') {}
# test4


@my_decorator(lambda x: 42, kw="any arg")
def func_5(arg): print(arg)


func_5("test5")
# Available inside the wrapper: (<function <lambda> at 0x7f2dbae3d870>,) {'kw': 'any arg'}
# test5


@my_decorator("arg_1", 2, [3, 4, 5], kwarg_1=1, kwarg_2="2")
def func_6(arg): print(arg)


func_6("test6")
# Available inside the wrapper: ('arg_1', 2, [3, 4, 5]) {'kwarg_1': 1, 'kwarg_2': '2'}
# test6


@my_decorator
@my_decorator()
@my_decorator("stacked")
@my_decorator("arg_1", 2, [3, 4, 5], kwarg_1=1, kwarg_2="2")
def func_7(arg): print(arg)


func_7("test7")
# Available inside the wrapper: () {}
# Available inside the wrapper: () {}
# Available inside the wrapper: ('stacked',) {}
# Available inside the wrapper: ('arg_1', 2, [3, 4, 5]) {'kwarg_1': 1, 'kwarg_2': '2'}
# test7

It worth checking Making decorators with optional arguments and its duplicates for more comprehensive answers.

A generic decorator skeleton code with comments and tests which tries to summarize all possible use cases:

from functools import wraps


def my_decorator(*args_or_func, **decorator_kwargs):
    """Originally from:
     https://stackoverflow.com/questions/5929107/decorators-with-parameters/65512042#65512042
    with hints from:
     https://stackoverflow.com/questions/3888158/making-decorators-with-optional-arguments/24617244#24617244"""
    def _decorator(func):
        @wraps(func)  # See: https://docs.python.org/3/library/functools.html#functools.wraps
        def wrapper(*args, **kwargs):
            # Do things before executing the function
            print("Available inside the wrapper:", decorator_args, decorator_kwargs)

            # Execute the original function with its args
            result = func(*args, **kwargs)

            # Do more things after executing the function

            return result

        return wrapper

    # TODO If you need only @my_decorator() or @my_decorator(...) use the else branch only
    # TODO To mitigate TypeError on single callable positional argument allow only keyword arguments:
    #  def my_decorator(func=None, *, decorator_kwarg1='value1', decorator_kwarg2='value2'):  # No **kwargs possible!
    #  and below: if func is not None: _decorator(func) else: _decorator
    # To allow @my_decorator (i.e. without parentheses) WARNING: Use keyword argument for single callable parameter!
    if len(args_or_func) == 1 and len(decorator_kwargs) == 0 and callable(args_or_func[0]):
        # Here you can set default values for positional arguments
        decorator_args = ()
        return _decorator(args_or_func[0])
    else:
        decorator_args = args_or_func  # This "global" variable is used inside _decorator() which is defined above
        return _decorator              # Hint: The function's implementation is evaluated when the function executed


@my_decorator
def func_1(arg): print(arg)


func_1("test1")
# Available inside the wrapper: () {}
# test1


@my_decorator()
def func_2(arg): print(arg)


func_2("test2")
# Available inside the wrapper: () {}
# test2


# Single callable positional argument. BAD INVOCATION! -> TypeError
@my_decorator(lambda x: 42)
def func_3(arg): print(arg)


try:
    func_3("Single callable positional argument. BAD INVOCATION! -> TypeError")
except TypeError as e:
    print("test3")
    print(f"Catched TypeError: TypeError: {e}")
# test3
# Catched TypeError: TypeError: 'int' object is not callable


@my_decorator(lambda x: 42, "any arg")
def func_4(arg): print(arg)


func_4("test4")
# Available inside the wrapper: (<function <lambda> at 0x7f96d94ed870>, 'any arg') {}
# test4


@my_decorator(lambda x: 42, kw="any arg")
def func_5(arg): print(arg)


func_5("test5")
# Available inside the wrapper: (<function <lambda> at 0x7f2dbae3d870>,) {'kw': 'any arg'}
# test5


@my_decorator("arg_1", 2, [3, 4, 5], kwarg_1=1, kwarg_2="2")
def func_6(arg): print(arg)


func_6("test6")
# Available inside the wrapper: ('arg_1', 2, [3, 4, 5]) {'kwarg_1': 1, 'kwarg_2': '2'}
# test6


@my_decorator
@my_decorator()
@my_decorator("stacked")
@my_decorator("arg_1", 2, [3, 4, 5], kwarg_1=1, kwarg_2="2")
def func_7(arg): print(arg)


func_7("test7")
# Available inside the wrapper: () {}
# Available inside the wrapper: () {}
# Available inside the wrapper: ('stacked',) {}
# Available inside the wrapper: ('arg_1', 2, [3, 4, 5]) {'kwarg_1': 1, 'kwarg_2': '2'}
# test7
浅唱々樱花落 2024-11-12 23:07:49

如果函数和装饰器都必须接受参数,您可以遵循以下方法。

例如,有一个名为 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)

在上面的代码中

  • timesdecorator1 的参数
  • a, bfunc1 的参数

In case both the function and the decorator have to take arguments you can follow the below approach.

For example there is a decorator named decorator1 which takes an argument

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

func1(1, 2)

Now if the decorator1 argument has to be dynamic, or passed while calling the function,

def func1(arg1, arg2):
    print (arg1, arg2)


a = 1
b = 2
seconds = 10

decorator1(seconds)(func1)(a, b)

In the above code

  • seconds is the argument for decorator1
  • a, b are the arguments of func1
自此以后,行同陌路 2024-11-12 23:07:49

匿名设置中使用参数进行装饰。

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

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

这两个函数的调用都在元组中进行。默认情况下,返回值是目标函数的结果。

语法糖修饰 @first_internal(send_msg)('...end') 所需版本 >= 3.9,参见 PEP 614 放宽装饰器的语法限制。

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

备注

  • 组合装饰器,例如回拉和前推(也许用更计算机科学的术语来说:同变和反变装饰器) ,可能更有用,但需要特别注意,例如组合规则,检查哪些参数在哪里,等等

  • 语法糖充当目标函数的一种部分:一旦装饰就没有回头路(没有额外的导入),但这不是强制性的,装饰器也可以以其扩展形式s使用,即first_external(send_msg)("start..." )(测试函数)(2)

  • 使用 timeit.repeat(..., Repeat=5, number=10000) 的工作台结果,比较了经典的 deflambda装饰显示几乎相同:

    • for lambda: [6.200810984999862, 6.035239247000391, 5.346362481000142, 5.987880147000396, 5.5331550319997405] - mean -> 5.8206

    • 对于def[6.165001932999985, 5.554595884999799, 5.798066574999666, 5.678178028000275, 5.446507932999793] - 平均值 -> 5.7284

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

Decoration with parameters in an anonymous setting.

Among of the many possibilities two variations of a "nested" syntactic sugar decoration are presented. They differ from each other by the order of execution wrt to the target function and their effects are, in general, independent (non interacting).

The decorators allow an "injection" a of custom function either before or after the execution of the target function.

The calls of both functions take place in a tuple. As default, the return value is the result of the target function.

The syntactic sugar decoration @first_internal(send_msg)('...end') required version >= 3.9, see PEP 614 Relaxing Grammar Restrictions On Decorators.

Used functools.wraps to keep the doc-string of the target function.

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)

Output

msg> start...
from test_function
msg> ...end

Remarks

  • composition decorators, such as pull-back and push-forward (maybe in a more Computer Science terminology: co- and resp. contra-variant decorator), could more useful but need ad-hoc care, for example composition rules, check which parameters go where, etc

  • syntactic sugar acts as a kind of partial of the target function: once decorated there is no way back (without extra imports) but it is not mandatory, a decorator can be used also in its extended forms, i.e. first_external(send_msg)("start...")(test_function)(2)

  • the results of a workbench with timeit.repeat(..., repeat=5, number=10000) which compare the classical def and lambda decoration shows that are almost equivalent:

    • for lambda: [6.200810984999862, 6.035239247000391, 5.346362481000142, 5.987880147000396, 5.5331550319997405] - mean -> 5.8206

    • for def: [6.165001932999985, 5.554595884999799, 5.798066574999666, 5.678178028000275, 5.446507932999793] - mean -> 5.7284

  • naturally an non-anonymous counterpart is possible and provides more flexibility

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文