高频考点-Python中的装饰器的用法?
这是一个非常经典且重要的Python进阶知识点。
一句话理解:装饰器就是一个“给函数增加额外功能,但不修改函数内部代码”的工具。就像给手机套上手机壳——手机本身没变,但多了防摔功能。
1. 基础认知:一切皆函数
在讲装饰器前,先记住两点:
Python中,函数可以被当作参数传给另一个函数。
Python中,函数可以嵌套定义。
2. 手动实现一个最简单的装饰器
假设你想知道一个函数运行了多长时间,但不想在每个函数里都写计时代码。
import time # 这是一个装饰器函数 def timer(func): def wrapper(*args, **kwargs): # *args, **kwargs 保证能接收任意参数 start = time.time() result = func(*args, **kwargs) # 真正执行原函数 end = time.time() print(f"函数 {func.__name__} 运行了 {end - start:.4f} 秒") return result return wrapper # 使用装饰器(语法糖 @) @timer def slow_function(): time.sleep(1) return "完成" # 调用时,完全无感知 print(slow_function()) # 输出:函数 slow_function 运行了 1.0002 秒 # 完成背后原理:@timer等价于执行了slow_function = timer(slow_function),也就是把原函数替换成了wrapper。
3. 带参数的装饰器(三层结构)
如果你的装饰器本身需要接收参数(比如指定超时时间),需要再包装一层:
def repeat(times): # 这是装饰器工厂,接收参数 def decorator(func): # 这是真正的装饰器 def wrapper(*args, **kwargs): for _ in range(times): result = func(*args, **kwargs) return result return wrapper return decorator @repeat(times=3) # 调用时带上参数 def say_hello(): print("Hello") say_hello() # 会打印 3 次 Hello4. 保留原函数的元信息(重要!)
使用装饰器后,原函数的__name__、__doc__等会被wrapper覆盖,调试时会困惑。标准做法是加上functools.wraps:
from functools import wraps def timer(func): @wraps(func) # 这一行把原函数的信息复制给 wrapper def wrapper(*args, **kwargs): # ... 计时逻辑 return func(*args, **kwargs) return wrapper5. 类装饰器(用类来实现)
除了用函数,还可以用类来实现装饰器,通过__call__魔法方法:
class CountCalls: def __init__(self, func): self.func = func self.count = 0 def __call__(self, *args, **kwargs): self.count += 1 print(f"第 {self.count} 次调用") return self.func(*args, **kwargs) @CountCalls def test(): print("执行") test() # 第 1 次调用 test() # 第 2 次调用6. 实际开发中最常用的场景
| 场景 | 说明 |
|---|---|
| 登录校验 | 检查用户是否登录,未登录则跳转 |
| 权限控制 | 检查当前用户是否有权限执行该操作 |
| 日志记录 | 自动记录函数的入参、返回值、异常 |
缓存(如@lru_cache) | 对相同参数直接返回缓存结果 |
| 重试机制 | 函数报错时自动重试 N 次 |
| 路由注册(Flask/FastAPI) | @app.route('/')就是装饰器 |
7. 一个常见的坑:装饰器顺序
多个装饰器叠加时,执行顺序是自下而上(从靠近函数的开始):
@a @b @c def func(): pass # 实际执行顺序:func = a(b(c(func)))8. 如果你刚接触,给你一个万能模板
以后写任何装饰器,直接套这个模板,不会出错:
from functools import wraps def my_decorator(func): @wraps(func) def wrapper(*args, **kwargs): # 在调用前做点什么 result = func(*args, **kwargs) # 在调用后做点什么 return result return wrapper最后送你一个思考:装饰器本质上是“面向切面编程”(AOP)的一种实现,它把业务逻辑(核心功能)和辅助逻辑(日志、计时、校验)分离开,让代码更干净。
