使用带或不带括号的 python 装饰器

Posted

技术标签:

【中文标题】使用带或不带括号的 python 装饰器【英文标题】:Using python decorator with or without parentheses 【发布时间】:2016-06-05 00:03:09 【问题描述】:

在 Python 中,使用相同的装饰器带括号和不带括号有什么区别?

例如:

不带括号:

@some_decorator
def some_method():
    pass

带括号:

@some_decorator()
def some_method():
    pass

【问题讨论】:

下面的答案很好。值得指出的区别与foofoo() anywhere 之间的区别相同,包括不在装饰器中。 【参考方案1】:

第一个代码中的some_decoratorsn -p 是一个正则装饰器:

@some_decorator
def some_method():
    pass

等价于

some_method = some_decorator(some_method)

另一方面,第二个代码 sn-p 中的some_decorator 是一个返回装饰器的可调用对象:

@some_decorator()
def some_method():
    pass

等价于

some_method = some_decorator()(some_method)

正如 Duncan 在 cmets 中指出的那样,一些装饰器被设计为双向工作。这是此类装饰器的一个非常基本的实现:

def some_decorator(arg=None):
    def decorator(func):
        def wrapper(*a, **ka):
            return func(*a, **ka)
        return wrapper

    if callable(arg):
        return decorator(arg) # return 'wrapper'
    else:
        return decorator # ... or 'decorator'

pytest.fixture 是一个更复杂的例子。

【讨论】:

可能值得添加到您的答案中,以提及某些装饰器是为双向工作而编写的,例如@pytest.fixture 可以直接用作装饰器,也可以使用一些命名参数调用,在这种情况下它会返回一个装饰器。 可调用装饰器的意义何在? AFAICT,someDecorator() 将始终返回相同的内容,因为它不接受任何参数(除非someDecorator 持有状态)。所以我认为可调用的装饰器持有一个状态? 还要注意,Duncan 提到的技巧现在可以完全自动化,这要感谢decopatch。顺便说一句,我是作者:)【参考方案2】:

简而言之,装饰器允许向函数和类组添加丰富的特性,而无需修改它们。

理解@some_decorator@some_decorator()区别的关键在于前者是装饰器,而后者是返回装饰器的函数(或可调用)

我相信看到每个案例的实现有助于理解差异:

@some_decorator

def some_decorator(func):
    def wrapper(func):
        return func(*args, **kwargs)
    return wrapper

应用:

@some_decorator
def some_method():
    pass

等价性:

some_method = some_decorator(some_method)

@some_decorator()

def some_decorator():
    def decorator(func):
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        return wrapper
    return decorator

应用:

@some_decorator()
def some_method():
    pass

等价性:

some_method = some_decorator()(some_method)

请注意,现在更容易看出@some_decorator() 是一个返回装饰器的函数,而some_decorator 只是一个装饰器。请记住,有些装饰器是为双向工作而编写的。

所以现在你可能想知道为什么我们有这两种情况,而前一个版本看起来更简单。答案是,如果你想将参数传递给装饰器,使用@some_decorator() 将允许你这样做。让我们看看一些实际的代码:

def some_decorator(arg1, arg2):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(arg1)
            print(arg2)
            return func(*args, **kwargs)
        return wrapper
    return decorator

应用:

@some_decorator('hello', 'bye')
def some_method():
    pass

等价性:

some_method = some_decorator('hello', 'bye')(some_method)

注意:我认为值得一提的是,装饰器可以实现为函数或类。更多信息请查看this。

【讨论】:

【参考方案3】:

如果你有一个可以带参数或不带参数的装饰器,你可以在你的装饰器上使用下面的装饰器让它可以带或不带括号使用,像这样:

>>> @omittable_parentheses(allow_partial=True)
... def multiplier(multiply_by=2):
...     def decorator(func):
...         def multiplying_wrapper(*args, **kwargs):
...             return multiply_by * func(*args, **kwargs)
...         return multiplying_wrapper
...     return decorator
...
>>> @multiplier
... def no_parentheses():
...     return 2
...
>>> no_parentheses()
4
>>> @multiplier()
... def parentheses():
...     return 2
...
>>> parentheses()
4
>>> @multiplier(3)
... def parameter():
...     return 2
...
>>> parameter()
6

如果给定了allow_partial=True,则生成的装饰器也将与partial一起使用:

>>> from functools import partial
>>> multiply_by_3 = partial(multiplier, multiply_by=3)
>>>
>>> @multiply_by_3
... def partial_no_parentheses():
...     return 2
...
>>> partial_no_parentheses()
6
>>> @multiply_by_3()
... def partial_parentheses():
...     return 2
...
>>> partial_parentheses()
6

装饰器代码:

from functools import wraps

def omittable_parentheses(maybe_decorator=None, /, allow_partial=False):
    """A decorator for decorators that allows them to be used without parentheses"""
    def decorator(func):
        @wraps(decorator)
        def wrapper(*args, **kwargs):
            if len(args) == 1 and callable(args[0]):
                if allow_partial:
                    return func(**kwargs)(args[0])
                elif not kwargs:
                    return func()(args[0])
            return func(*args, **kwargs)
        return wrapper
    if maybe_decorator is None:
        return decorator
    else:
        return decorator(maybe_decorator)

作为奖励,这个装饰器装饰器本身可以带或不带括号使用!

【讨论】:

【参考方案4】:

在装饰器中使用 arg 的一些实际工作代码:

def someDecorator(arg=None):
    def decorator(func):
        def wrapper(*a, **ka):
            if not callable(arg):
                print (arg)
                return func(*a, **ka)
            else:
                return 'xxxxx'
        return wrapper

    if callable(arg):
        return decorator(arg) # return 'wrapper'
    else:
        return decorator # ... or 'decorator'

@someDecorator(arg=1)
def my_func():
    print('aaa')

@someDecorator
def my_func1():
    print('bbb')

if __name__ == "__main__":
    my_func()
    my_func1()

输出是:

1
aaa

【讨论】:

这是一个非常好的技术,但它并没有真正回答原始问题,即使用括号与不使用括号有什么区别。

以上是关于使用带或不带括号的 python 装饰器的主要内容,如果未能解决你的问题,请参考以下文章

如何使用可选参数构建装饰器? [复制]

装饰器,调用不带括号的函数?

如何创建一个可以使用或不使用参数的 Python 装饰器?

python 带参与不带参装饰器的使用

在 ES6 中导入带或不带大括号 [重复]

关于Python装饰器内层函数为什么要return目标函数的一些个人见解