Python基础:装饰器
一、知识点详解
1. 装饰器是什么?
本质定义:
装饰器是接收函数并返回新函数的高阶函数。
它能够通过@语法糖在不改变原函数代码的前提下,给原函数添加新的功能。核心原理:
利用闭包(内层函数引用外层函数变量)和函数作为对象(一等公民)的特性,实现“包装器”逻辑。语法糖本质:
“语法糖”是一种编程特性,它能将某些代码操作以简洁且优雅的形式呈现。
例如,在装饰器使用场景中,@decorator作为func = decorator(func)的简写形式,
通过直接在函数定义上方使用@符号,就能便捷地绑定装饰器,这里的@便是语法糖的体现。
--
此外,Python 里还有不少语法糖,
像[x for x in range(10)]这样的推导式(用于列表、字典等快速生成)、
a if condition else b这种条件表达式(即三元运算符),
以及if (n:=len(data)) > 0:中的海象运算符(:=),可在条件判断中同时完成赋值操作等。
2. 为什么使用装饰器
使用装饰器主要有以下好处:
代码复用:多个函数需要相同额外功能时,封装成装饰器避免代码重复。
代码简洁:分离额外功能与核心逻辑,原函数专注于核心功能的实现。
易于维护:修改额外功能时只需调整装饰器,无需逐个修改被装饰函数。
3. 装饰器怎样使用
装饰器使用“三步走”:
-
定义装饰器函数:
接收目标函数为参数,返回新的包装函数。 -
定义原函数:
编写需增强功能的核心函数。 -
应用装饰器:
在原函数定义前使用@装饰器名,等价于原函数 = 装饰器(原函数)。
4. 应用场景
计时:记录函数执行时间(使用
time.time()计算时间差)。权限校验:根据参数校验用户角色,抛出异常(如
PermissionError)。类型检查:通过函数注解
@enforce_types验证参数类型(利用func.__annotations__)。返回值增强:修改原函数返回值(如自动加固定值)。
5. 常见错误
-
错误调用函数:
@decorator不是@decorator(),装饰器直接绑定函数而非调用 -
参数传递错误:未使用
*args, **kwargs导致参数丢失,需在包装函数中兼容任意参数 - 循环导入:装饰器和被装饰函数相互引用时拆分代码,避免模块间循环依赖
二、说明示例
1. 无参数装饰器 (基本用法)
通过闭包定义内层包装函数,在调用原函数前后添加逻辑(如计时),返回包装函数。
import time
def timer(func):
def wrapper():
start = time.time() # 记录开始时间
result = func() # 执行原函数
print(f"耗时:{time.time()-start:.2f}秒") # 计算耗时
return result # 显式返回原函数结果
return wrapper
@timer
def slow_func():
time.sleep(1)
slow_func() # 输出执行时间
2. 保留原函数元信息
使用 functools.wraps 装饰包装函数,避免原函数的名称、文档字符串等元信息被覆盖。
from functools import wraps
def decorator(func):
@wraps(func) # 保留原函数元信息(函数名、注释等)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@decorator
def example():
"""示例函数"""
pass
print(example.__name__) # 输出"example"(而非默认的"wrapper")
3. 处理带参数的函数
包装函数使用 *args, **kwargs 接收任意参数,确保装饰器兼容不同参数形式的函数。
from functools import wraps
def logger(func):
@wraps(func) # 保留原函数元信息(如函数名、文档字符串)
def wrapper(*args, **kwargs):
print(f"调用函数:{func.__name__},参数:{args}, {kwargs}")
return func(*args, **kwargs) # 传递参数并返回原函数结果
return wrapper
@logger
def add(a, b):
return a + b
print(add(2, b=3)) # 输出参数和结果
4. 带参数的装饰器
通过三层嵌套函数,外层接收装饰器参数,中层绑定目标函数,内层包装函数实现参数化逻辑(如重复执行函数n次)。
from functools import wraps
def repeat(n):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
results = []
# 循环n次调用原函数
for _ in range(n):
results.append(func(*args, **kwargs))
return results # 返回最终结果而非None
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"Hello {name}") # 打印欢迎语句
return f"Hello {name}" # 返回欢迎语句
greet("小帅") # 输出3次"Hello 小帅"
print(greet("小帅")) # ['Hello 小帅', 'Hello 小帅', 'Hello 小帅']
三、知识点总结
装饰器的本质与核心原理
装饰器是接收函数并返回新函数的高阶函数,利用闭包和函数作为“一等公民”的特性,在不修改原函数代码的前提下添加新功能。语法糖的本质
@decorator是func = decorator(func)的简写,通过简洁符号实现装饰器与函数的绑定,类似Python中推导式、三元运算符等简化代码的语法特性。使用装饰器的优势
通过封装通用功能(如计时、日志)实现代码复用,分离核心逻辑与额外功能,提升代码简洁性和可维护性。装饰器的使用步骤
定义接收目标函数并返回包装函数的装饰器 → 编写需增强的原函数 → 用@装饰器名绑定(等价于函数重赋值)。
四、扩展知识
4.1 多层装饰器
def decorator1(func):
def wrapper1(*args, **kwargs):
print("进入外层装饰器 wrapper1")
print("校验权限...")
# 这里可加入权限校验逻辑
result = func(*args, **kwargs) # 调用 decorator2 的 wrapper2
print("离开外层装饰器 wrapper1")
return result
return wrapper1
def decorator2(func):
def wrapper2(*args, **kwargs):
print("进入内层装饰器 wrapper2")
result = func(*args, **kwargs) # 调用原始函数
print("记录操作日志...")
# 这里可加入记录操作逻辑
print("离开内层装饰器 wrapper2")
return result
return wrapper2
@decorator1
@decorator2
def my_func():
print("执行原始函数")
# 调用被装饰后的函数
my_func()
执行流程说明:
- 装饰器绑定:
my_func = decorator1(decorator2(my_func))
- 调用
my_func()时的实际执行顺序:- 进入
decorator1的wrapper1 -
wrapper1中执行权限校验逻辑 -
wrapper1调用decorator2的wrapper2 -
wrapper2调用原始函数my_func -
wrapper2中执行日志记录逻辑 - 以上执行完毕后,以相反顺序返回
- 离开
decorator2的wrapper2 - 离开
decorator1的wrapper1
- 进入
输出结果:
进入外层装饰器 wrapper1
校验权限...
进入内层装饰器 wrapper2
执行原始函数
记录操作日志...
离开内层装饰器 wrapper2
离开外层装饰器 wrapper1
总结:
多层装饰器按 @外层 @内层 顺序绑定,调用时从外层包装函数向内层执行,最终调用原函数(等价于嵌套函数调用)。
4.2 装饰器函数的执行时机
装饰器函数在模块加载时立即执行(仅执行一次),用于绑定被装饰函数,而 wrapper 函数在每次调用被装饰函数时执行。
def deco1(func):
print("装饰器1初始化") # 绑定阶段第二步:(绑定顺序自下而上)
def wrapper1():
print("执行装饰器1逻辑") # 调用阶段第一步:(调用顺序自上而下)
return func() # 调用内层装饰器的wrapper2
return wrapper1
def deco2(func):
print("装饰器2初始化") # 绑定阶段第一步:(绑定顺序自下而上)
def wrapper2():
print("执行装饰器2逻辑") # 调用阶段第二步:(调用顺序自上而下)
return func() # 调用原始函数target
return wrapper2
@deco1 # 等价于 target = deco1(deco2(target))
@deco2 # 先执行deco2(target),再将结果传给deco1
def target():
print("执行目标函数") # 调用阶段第三步:(调用顺序自上而下)
target() # 触发装饰器链的执行
以上代码执行流程
| 阶段 | 执行步骤 | 输出结果 |
|---|---|---|
| 绑定阶段 | 执行deco2(target)
|
打印"装饰器2初始化" |
执行deco1(wrapper2)
|
打印"装饰器1初始化" | |
| 调用阶段 | 执行wrapper1()
|
打印"执行装饰器1逻辑" |
执行wrapper2()
|
打印"执行装饰器2逻辑" | |
执行target()
|
打印"执行目标函数" |