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

87
在Python中,使用相同的装饰器带有和不带括号有什么区别?
例如:
不带括号:
@some_decorator
def some_method():
    pass

带括号:

@some_decorator()
def some_method():
    pass

2
下面是个很棒的回答。值得指出的是,它与 foofoo() 之间的差别是一样的,无论在什么地方,甚至不在装饰器中。 - Mike Graham
4个回答

92

some_decorator在第一个代码片段中是一个常规装饰器:

@some_decorator
def some_method():
    pass

相当于

some_method = some_decorator(some_method)

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

@some_decorator()
def some_method():
    pass

等同于

some_method = some_decorator()(some_method)

正如 Duncan 在评论中指出的那样,一些装饰器设计成可以双向工作。下面是这样一个装饰器的基本实现:

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是一个更复杂的例子。


15
值得一提的是,有些装饰器可以双向使用,例如 @pytest.fixture 可以直接用作装饰器,或者带有一些命名参数调用它,这样它就会返回一个装饰器。 - Duncan
可调用装饰器的意义是什么?据我所知,someDecorator() 总是返回相同的结果,因为它不接受任何参数(除非 someDecorator 保存了状态)。因此,我认为可调用装饰器保存了状态? - Utku
2
还要注意,Duncan提到的技巧现在完全可以通过decopatch进行自动化。顺便说一下,我是这个库的作者 :) - smarie

13
简单来说,装饰器允许在不修改函数和类的情况下为它们添加丰富的功能。理解@some_decorator@some_decorator()之间的区别的关键是前者是装饰器,而后者是返回装饰器的函数(或可调用对象)。我认为看到每种情况的实现有助于理解它们之间的区别:

@some_decorator

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

应用:

@some_decorator
def some_method():
    pass

等价性:

some_method = some_decorator(some_method)

@some_decorator()

这段代码是Python中的一个装饰器语法,用于在函数定义上方应用一个装饰器。
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)

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


我对此答案感到困惑。在你的第一个没有括号的示例中,你使用了“args”和“kwargs”,但它们在任何地方都没有被定义。在有括号的示例中,我能看出它们是从哪里来的,但在没有括号的示例中却不清楚。 - David Parks
1
经过测试,我认为这只是一个打字错误,我编辑了答案进行更正。 - David Parks

4
如果您有一个可用于带或不带参数的装饰器,您可以在您的装饰器上使用以下装饰器,使其能够带或不带括号使用,如下所示:
>>> @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)

作为额外的奖励,这个装饰器装饰器可以自行决定是否使用括号!

-1

一些实际工作的代码,其中您在装饰器内使用参数:

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

这是一种非常好的技巧,但它并没有真正回答原始问题,即使用括号和不使用括号之间的区别是什么。 - florisla

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接