Python - 装饰器应用的正确顺序

2 投票
3 回答
1241 浏览
提问于 2025-04-16 05:05

我正在装饰一个函数,像这样:

def some_abstract_decorator(func):
    @another_lower_level_decorator
    def wrapper(*args, **kwargs):
        # ... details omitted
        return func(*args, **kwargs)
    return wrapper

这段代码的效果是你想要的(它应用了一个低级的装饰器,然后做了一些其他的事情)。我的问题是,我现在想使用 functools.wraps,但我不知道该放在哪里。这个是我的猜测,但我不确定这样做会不会有意想不到的后果。

def some_abstract_decorator(func):
    @wraps(func)
    @another_lower_level_decorator
    def wrapper(*args, **kwargs):
        # ... details omitted
        return func(*args, **kwargs)
    return wrapper

(当然,我也会在 another_lower_level_decorator 里面使用 wraps

3 个回答

1

是的,我觉得这样没问题。@another_lower_level_decorator 会返回一个函数,然后 @wraps 会把这个函数包装起来,这样它的名字就和 func 一样了。

2

没错,这个过程是这样的:

  • wrapper 被定义了。它会用自己的参数去调用 func
  • another_lower_level_decorator 被调用,传入 wrapper 作为参数。它返回的函数会成为新的 wrapper 的值。
  • 接着调用 wraps(func),这个函数会创建一个包装器,用来把 func 的名字、文档字符串等信息应用到它所调用的任何函数上。
  • wraps(func) 的返回值,也就是生成的包装函数,会接收当前的 wrapper 值。记住,这个值是来自 another_lower_level_decorator 的返回值。
  • wraps(func)(wrapper) 变成了新的 wrapper 的值。
  • 这个值会被 some_abstract_decorator 返回,使得这个函数可以用作装饰器。

大致就是这样。我觉得在实际操作中,wrapper 只会被重新赋值一次。

2

试试看:

from functools import wraps    

def another_lower_level_decorator(func):
    @wraps( func )
    def wrapped(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapped

def some_abstract_decorator(func):
    @wraps(func)
    @another_lower_level_decorator
    def wrapper(*args, **kwargs):
        # ... details omitted
        return func(*args, **kwargs)
    return wrapper


@some_abstract_decorator
def test():
    """ This is a docstring that should be on the decorated function """
    pass

help(test)

输出结果:

Help on function test in module __main__:

test(*args, **kwargs)
    This is a docstring that should be on the decorated function

你可以看到,它运行得很好!文档字符串和分配的名称都在。

不过,这样做也是一样的效果:

def some_abstract_decorator(func):
    @another_lower_level_decorator
    @wraps(func)
    def wrapper(*args, **kwargs):
        # ... details omitted
        return func(*args, **kwargs)
    return wrapper

wraps 只是用来修复文档字符串和名称的。只要所有的装饰器都使用 wraps,那么你应用它的顺序就不重要了。

顺便说一下,这里有一个更酷的装饰器库:点击查看

from decorator import decorator

@decorator
def another_decorator(func, *args, **kwargs):
    return func(*args, **kwargs)

@decorator
@another_decorator
def some_abstract_decorator(func, *args, **kwargs):
    # ... details omitted
    return func(*args, **kwargs)


@some_abstract_decorator
def test(x):
    """ this is a docstring that should be on the decorated function """
    pass

撰写回答