第 33 课 · 阶段五 · 文件与异常
装饰器
装饰器能在不修改函数内部代码的前提下,给函数“加功能”,是 Python 最优雅的高级特性之一。这一课从原理到实战彻底吃透。
🎯 学完本课你将掌握
- 理解装饰器的原理(函数也是对象)
- 用 @ 语法编写装饰器
- 编写带参数的装饰器与 functools.wraps
一、函数也是对象
Python 里函数是一等公民:可以当作参数传递、可以赋值给变量、可以定义在函数内部。这是装饰器的基础。
函数即对象.py
1def say_hello():2 print("你好")34f = say_hello # 赋值给变量5f() # 你好67def call_twice(func):8 func()9 func()1011call_twice(say_hello) # 把函数当参数传二、装饰器的本质
装饰器就是一个接收函数、返回新函数的函数。新函数在调用原函数前后插入额外逻辑。
手写装饰器.py
1def my_decorator(func):2 def wrapper():3 print("=== 函数开始前 ===")4 func()5 print("=== 函数结束后 ===")6 return wrapper78@my_decorator # 等价于 say = my_decorator(say)9def say():10 print("我是原函数")1112say()三、装饰带参数的函数
带参数.py
1def my_decorator(func):2 def wrapper(*args, **kwargs):3 print("开始调用...")4 result = func(*args, **kwargs)5 print("调用结束")6 return result # 记得把结果传回去!7 return wrapper89@my_decorator10def add(a, b):11 return a + b1213print(add(3, 5)) # 8✅ 要点
通用 wrapper(*args, **kwargs) 能接收任意参数,并 return 原函数结果——这是标准写法。
四、实战:计时装饰器
计时器.py
1import time23def timer(func):4 def wrapper(*args, **kwargs):5 start = time.time()6 result = func(*args, **kwargs)7 print(f"{func.__name__} 耗时 {time.time() - start:.4f} 秒")8 return result9 return wrapper1011@timer12def slow_sum(n):13 return sum(range(n))1415print(slow_sum(1000000))五、带参数的装饰器(三层嵌套)
带参数装饰器.py
1def repeat(times):2 def decorator(func):3 def wrapper(*args, **kwargs):4 for _ in range(times):5 func(*args, **kwargs)6 return wrapper7 return decorator89@repeat(3)10def hello():11 print("你好")1213hello() # 打印3次六、functools.wraps:保留原函数信息
wraps.py
1from functools import wraps23def my_decorator(func):4 @wraps(func) # 保留原函数的 __name__ 和文档5 def wrapper(*args, **kwargs):6 return func(*args, **kwargs)7 return wrapper89@my_decorator10def test():11 """我是测试函数"""1213print(test.__name__) # test(没有@wraps会是wrapper)七、常见错误与解决
常见错误与解决
| 错误现象 | 原因 / 解决方法 |
|---|---|
装饰器返回值不是函数 | 装饰器内部必须 return 一个函数,否则调用方拿到的是 None。 |
wrapper 没 return 原函数结果 | 带返回值的函数被装饰后变 None,记得 return result。 |
被装饰后函数信息丢失 | 用 @wraps(func) 保留原名和文档。 |
✍️ 小练习
写一个装饰器 check_login,模拟登录校验(没登录打印"请先登录",否则执行原函数);再写一个缓存装饰器(简单版)。
📌 本节小结
装饰器 = 高阶函数:接收函数、返回增强版函数。@语法糖让代码干净,wrapper(*args,**kwargs) 是标准模板,@wraps 保留元信息。日志、计时、鉴权、缓存全靠它。