前言
很早就接触装饰器了,刚接触的时候完全看不懂它的语法,搞不明白它的作用,尤其是看到多个装饰器可以装饰同一个函数,并且有的装饰器还能传参,经常被搞的一头雾水,后来有了一些自己的理解,也是只会简单的使用装饰器,但是自己编写还是要借助资料或AI,比较头疼。所以本文的目的是学习并掌握装饰器的anything,以达到对python的装饰器运用自如的境界
一、什么是装饰器
本质上就是一个函数,可以为其他函数增加额外功能
⚠️注意:装饰器返回的是一个函数,因为它的作用就是给传入的原函数增加额外的功能
二、为什么要用装饰器
一看就懂的例子:比如说10个模特要依次出场,每个模特都需要带一个手链,但是买10个手链成本又很高,可以只买一个手链让她们轮流复用即可。看到这里大概就知道为什么要用装饰器了吧😃
1. 代码复用与模块化
装饰器允许你将一些通用功能(如日志记录、性能测试、权限验证等)抽离出来,形成独立的装饰器函数,然后可以轻松地应用到多个目标函数上。
例如,如果你需要为多个函数添加 “执行时间统计” 功能,无需在每个函数中重复编写计时代码,只需定义一个计时装饰器,再用它修饰这些函数即可。
2. 保持代码简洁与可读性
如果不使用装饰器,要给函数添加额外功能,通常有两种方式:
- 直接修改函数内部代码(破坏了原函数的封装性)
- 在调用函数时手动添加额外逻辑(导致代码冗余且分散)
装饰器通过 @装饰器名 的语法,清晰地表明了 “这个函数被增强了”,既不侵入原函数代码,又让增强逻辑的位置非常明确。
3. 面向切面编程(AOP)
装饰器是实现 AOP 思想的重要工具。AOP 强调将 “核心业务逻辑” 与 “辅助功能”(如日志、事务、缓存)分离,装饰器恰好能实现这种分离:
- 核心逻辑写在原函数中
- 辅助功能写在装饰器中
- 通过装饰器将两者 “编织” 在一起
三、如何编写&调用
1. 不带参数的装饰器
- 简单编写一个 统计函数耗时装饰器
elapsed:
import time
def elapsed(func):
"""目标函数执行的耗时"""
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"函数 {func.__name__} 执行耗时: {end_time - start_time:.6f} 秒")
return result
return wrapper
可以发现,装饰器函数的内部wrapper函数的型参使用的是万能型参 *args, **kwargs保证函数的参数可以正确传入
- 调用这个
elapsed装饰器
@elapsed
def power_sum(n):
"""计算 1~n 的平方和"""
total = 0
for i in range(1, n + 1):
total += i ** 2
return total
💡@elapsed 是语法糖的写法 等价于下面的调用
power_sum = elapsed(power_sum)
- 输出
函数 power_sum 执行耗时: 1.281313 秒
41666667916666675000000
2. 带参数的装饰器
带参数的装饰器可以理解为一个 “装饰器工厂”,它先接收参数,然后返回一个真正的装饰器函数。这种装饰器的实现需要三层函数嵌套:
- 最外层函数:接收装饰器的参数
- 中间层函数:真正的装饰器,接收被装饰的函数
- 最内层函数:包装函数,实现具体的装饰逻辑
- 改进的统计函数耗时装饰器
elapsed(可以通过参数指定运行时间小数点精度的):
import time
def elapsed_precision(precision=6):
def decorator(func):
"""目标函数执行的耗时"""
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"函数 {func.__name__} 执行耗时: {round(end_time - start_time, precision)} 秒")
return result
return wrapper
return decorator
- 调用这个
elapsed_precisioin装饰器
@elapsed_precision(3) # 保留三位小数
def power_sum(n):
"""计算 1~n 的平方和"""
total = 0
for i in range(1, n + 1):
total += i ** 2
return total
- 输出
函数 power_sum 执行耗时: 1.322 秒
41666667916666675000000
四. 装饰器运行的顺序
在函数定义阶段:执行顺序是从最靠近函数的装饰器开始,自内而外的执行
在函数执行阶段:执行顺序由外而内,一层层执行
def deco1(func):
def wrapper():
print('deco1 start')
func()
print('deco1 end')
return wrapper
def deco2(func):
def wrapper():
print('deco2 start')
func()
print('deco2 end')
return wrapper
@deco1
@deco2
def my_func():
print('original function')
my_func()
该示例中,my_func 函数被 deco1 和 deco2 装饰器修饰。在函数执行时,实际上等价于以下代码:
my_func = deco1(deco2(my_func))
my_func()
运行结果如下:
deco1 start
deco2 start
original function
deco2 end
deco1 end
很难用语言描述这个顺序,意会一下。因为定义的时候返回的是函数对象,并没有执行,然后在执行的时候就是最外层最先开始执行。
五、常见使用场景
- 日志记录:自动记录函数的调用参数、返回值、调用时间等
- 性能分析:统计函数的执行时间
- 权限验证:在函数执行前检查用户是否有权限
- 缓存:缓存函数的计算结果,避免重复计算
- 输入验证:检查函数参数是否符合要求