python中多个装饰器的调用顺序详解

1. 概述

在Python中,装饰器是非常有用的编程工具,可以用于添加功能,改变现有函数的行为等等。在一些情况下,我们可能需要使用多个装饰器来装饰一个函数,如何保证装饰器的执行顺序就变得很重要。在这篇文章中,我们将深入分析Python中多个装饰器的调用顺序。

2. 内部包裹函数的装饰器调用顺序

装饰器顺序的重要性在于多个装饰器可用于装饰类或函数。然而,我们不能简单地把它们的执行顺序当成写在代码中的声明顺序。具体而言,内部包裹函数的装饰器的执行顺序是从内到外的。为了更好地理解这一点,让我们来看看下面这个例子。

def first_decorator(func):

def wrapper():

print("first_decorator wrapper start")

func()

print("first_decorator wrapper end")

return wrapper

def second_decorator(func):

def wrapper():

print("second_decorator wrapper start")

func()

print("second_decorator wrapper end")

return wrapper

@first_decorator

@second_decorator

def decorated():

print("Decorated")

decorated()

上面的代码定义了两个装饰器函数,分别是first_decorator和second_decorator。然后我们用@语法将decorated函数依次装饰了起来。运行这段代码后,我们会发现输出日志的顺序是先second_decorator的包裹函数,再是first_decorator的包裹函数。也就是说,第一个装饰器其实是最后执行的。

2.1 为什么调用顺序会这样?

在装饰器嵌套的情况下,Python会从里到外依次调用装饰器。当多个装饰器共同作用于一个函数时,它们会嵌套在一起,其中最内层的装饰器首先执行,然后是紧随其后的装饰器,以此类推,直到最外面的装饰器执行完毕。这种行为也被称为“装饰器链”。

为了进一步理解这一点,看一下下面这个例子。

def first_decorator(func):

print("first_decorator start")

def wrapper():

print("first_decorator wrapper start")

func()

print("first_decorator wrapper end")

print("first_decorator end")

return wrapper

def second_decorator(func):

print("second_decorator start")

def wrapper():

print("second_decorator wrapper start")

func()

print("second_decorator wrapper end")

print("second_decorator end")

return wrapper

@first_decorator

@second_decorator

def decorated():

print("Decorated")

decorated()

运行上述代码,并观察输出日志。我们可以得到以下输出结果。

输出:

second_decorator start

first_decorator start

first_decorator end

second_decorator end

first_decorator wrapper start

second_decorator wrapper start

Decorated

second_decorator wrapper end

first_decorator wrapper end

这里我们可以看到,装饰器的调用顺序与它们在代码中的声明顺序是相反的。这是因为,Python会先调用最内层的装饰器,也就是最后一个装饰器,以此类推。所以,当有多个装饰器存在时,我们应该注意它们的执行顺序,以便得到正确的结果。

3. 外部装饰函数的装饰器调用顺序

假设我们的装饰器本身也是一个带有参数的函数。在这种情况下,Python会首先调用外层函数,传递给它所有的参数。然后,在执行过程中,返回一个由参数调用的内部装饰器。在调用内部装饰器时,Python会遵循嵌套装饰器的规则。让我们看下面的例子,它演示了两个装饰器的情况。

def outer_decorator(arg):

print(f"outer_decorator got arg {arg}")

def inner_decorator(func):

print("inner_decorator start")

def wrapper():

print("inner_decorator wrapper start")

func()

print("inner_decorator wrapper end")

print("inner_decorator end")

return wrapper

return inner_decorator

@outer_decorator("abc")

def decorated():

print("Decorated")

decorated()

运行上述代码,我们可以得到以下输出结果。

outer_decorator got arg abc

inner_decorator start

inner_decorator end

inner_decorator wrapper start

Decorated

inner_decorator wrapper end

我们可以看到,调用顺序是外到内,与上一部分的情形正好相反。

4. 带参数的装饰器

在实际开发中,我们有时会使用带参数的装饰器来添加更多的自定义逻辑。这个时候,Python的装饰器执行顺序就变得更为复杂了。因此,为了解决这个问题,Python提供了一种既简单又有效的方式,即使用functools库中的wraps装饰器。

现在,让我们看看这个例子。

from functools import wraps

def parametrized_decorator_with_functools(arg):

print(f"parametrized_decorator_with_functools got arg {arg}")

def inner_decorator(func):

print("parametrized_decorator_with_functools start")

@wraps(func)

def wrapper():

print("parametrized_decorator_with_functools wrapper start")

func()

print("parametrized_decorator_with_functools wrapper end")

print("parametrized_decorator_with_functools end")

return wrapper

return inner_decorator

@parametrized_decorator_with_functools("abc")

def decorated():

print("Decorated")

decorated()

运行上述代码,我们可以得到以下输出结果。

parametrized_decorator_with_functools got arg abc

parametrized_decorator_with_functools start

parametrized_decorator_with_functools end

parametrized_decorator_with_functools wrapper start

Decorated

parametrized_decorator_with_functools wrapper end

在这个例子中,我们添加了一个新的装饰器,即wraps。这个装饰器的作用是将装饰器函数的名称、文档字符串和其他重要元数据绑定到外部包裹函数上。它的用法非常简单,只需在函数声明中使用@wraps装饰器即可。

4.1 functools.wraps的作用是什么?

当我们定义一个带有参数的装饰器时,我们的代码不再是一个简单的函数传递到另一个函数中。相反,它需要向内部函数中传递一些额外的元信息。但是,在这些信息的传递过程中,Python会覆盖掉原始函数的__name__、__doc__和__module__属性。这些属性中的每一个都被认为是特殊的,因为它们可以帮助我们对函数进行自我描述。但是,当我们使用简单而不带任何元信息的包裹函数时,这些信息会丢失。这时,我们就需要使用functools.wraps装饰器来保留这些特殊属性。

5. 总结

在这篇文章中,我们了解了Python中多个装饰器的调用顺序问题。我们看到,在Python中多个装饰器的调用顺序取决于它们的位置和嵌套关系。在定义装饰器时,我们应该牢记这些规则来正确地安排装饰器的顺序。此外,为了确保使用了带参数的装饰器的正确性,我们还应该使用functools.wraps来保存函数元信息。希望这篇文章对您有所帮助,加深对Python函数装饰器的理解。

免责声明:本文来自互联网,本站所有信息(包括但不限于文字、视频、音频、数据及图表),不保证该信息的准确性、真实性、完整性、有效性、及时性、原创性等,版权归属于原作者,如无意侵犯媒体或个人知识产权,请来电或致函告之,本站将在第一时间处理。猿码集站发布此文目的在于促进信息交流,此文观点与本站立场无关,不承担任何责任。

后端开发标签