【Python】- 通过一个装饰器示例分析装饰器的本质及特性

白猫打不过黑猫 / 2023-09-01 / 原文

装饰器

装饰器的简单理解:


############装饰器写法
@decorate
def target():
    print('running target()')

############等同于
def target():
 print('running target()')
target = decorate(target) # 将被装饰的函数作为参数传递给装饰器函数。然后返回装饰器函数对象。

通过一个装饰器示例来展示装饰器的特性及本质:
python_decorator.py

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Before function is called")
        result = func(*args, **kwargs)  # 调用原始函数
        print(f'func result is {result}')
        print("After function is called")
        result= 20
        return result
    return wrapper

@my_decorator
def add_numbers(a, b):
    print('running now add_numbers()')
    return a + b

result = add_numbers(3, 5)
print("Result:", result)

我们可以根据这段代码的运行流程来查看装饰器的特性:
在这里我们需要使用到https://pythontutor.com/render.html#mode=display这个网站。该网站可以一步一步的输出整段代码的运行流程

1、在加载模块时,可以看到第一步会先执行my_decorator这个装饰器函数的定义。然后将其存储在内存中。注意:这里是因为装饰器函数与被装饰器函数定义在同一个模块,Python会从上到下将变量或函数的定义保存到内存中,装饰器的实际执行的第一步应该是@my_decorator这部分。

2、随后会执行@my_decorator部分,这部分其实是将add_numbers()这个被装饰的函数作为参数传递给my_decorator(func)这个装饰器函数。

3、然后进入my_decorator装饰器函数,会执行wrapper()这个内部函数的定义。然后return wrapper函数对象。

4、result = add_numbers(),调用 add_numbers() 被装饰函数时,实际会跳转到装饰器函数 my_decorator return的内部函数 wrapper() 中。然后开始执行 wrapper() 这个内部函数,

5、result = func(*args, **kwargs) , 在 wrapper() 这个内部函数中调用了原始函数,也就是 add_numbers() 这个函数。然后将 add_numbers() 的return赋值给result这个变量。这时result的值为3+5=8。随后又将20赋值给result这个变量并进行返回。

6、print("Result:", result) 当正常运行add_numbers()函数时,本来应该是输出3+5=8的,但是因为装饰器的原因,运行add_numbers()函数时实际进入的是装饰器中的返回的内部函数wrapper(),而在内部函数wrapper()中我将result的结果修改为20,所以无论参数传的是多少,最后的resut都将是20。

从整个装饰器的运行流程以及最后返回的结果来看。可以很清晰的总结出装饰器的本质以及作用:

装饰器的定义:

  • 装饰器的本质就是一个包裹函数,用来修改或者增强被装饰的函数。
  • 装饰器是一种可调用对象,它的参数是被装饰的函数。
  • 装饰器可能会对被装饰的函数做相关处理,然后再返回该函数。或者会将被装饰的函数替换为另外一个函数或可调用对象。

装饰器的特性:

  • 装饰器是一个函数或者一个可调用对象。
  • 装饰器可以把被装饰的函数替换为其他的函数。
  • 装饰器会在所在模块被加载时运行。在流畅的Python中关于装饰器疑问中有提到装饰器的一个关键性质是,它们在被装饰的函数定义之后立即运行。这通常是在导入时(例如,当 Python 加载模块时)。