第 33 课 · 阶段五 · 文件与异常

装饰器

装饰器能在不修改函数内部代码的前提下,给函数“加功能”,是 Python 最优雅的高级特性之一。这一课从原理到实战彻底吃透。

第 33 课阶段五 · 文件与异常难度:进阶建议时长:40 分钟关键词:装饰器 · 语法糖 · functools

🎯 学完本课你将掌握

  • 理解装饰器的原理(函数也是对象)
  • 用 @ 语法编写装饰器
  • 编写带参数的装饰器与 functools.wraps

一、函数也是对象

Python 里函数是一等公民:可以当作参数传递、可以赋值给变量、可以定义在函数内部。这是装饰器的基础。

函数即对象.py
1def say_hello():
2 print("你好")
3
4f = say_hello # 赋值给变量
5f() # 你好
6
7def call_twice(func):
8 func()
9 func()
10
11call_twice(say_hello) # 把函数当参数传

二、装饰器的本质

装饰器就是一个接收函数、返回新函数的函数。新函数在调用原函数前后插入额外逻辑。

手写装饰器.py
1def my_decorator(func):
2 def wrapper():
3 print("=== 函数开始前 ===")
4 func()
5 print("=== 函数结束后 ===")
6 return wrapper
7
8@my_decorator # 等价于 say = my_decorator(say)
9def say():
10 print("我是原函数")
11
12say()

三、装饰带参数的函数

带参数.py
1def my_decorator(func):
2 def wrapper(*args, **kwargs):
3 print("开始调用...")
4 result = func(*args, **kwargs)
5 print("调用结束")
6 return result # 记得把结果传回去!
7 return wrapper
8
9@my_decorator
10def add(a, b):
11 return a + b
12
13print(add(3, 5)) # 8
✅ 要点
通用 wrapper(*args, **kwargs) 能接收任意参数,并 return 原函数结果——这是标准写法。

四、实战:计时装饰器

计时器.py
1import time
2
3def 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 result
9 return wrapper
10
11@timer
12def slow_sum(n):
13 return sum(range(n))
14
15print(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 wrapper
7 return decorator
8
9@repeat(3)
10def hello():
11 print("你好")
12
13hello() # 打印3次

六、functools.wraps:保留原函数信息

wraps.py
1from functools import wraps
2
3def my_decorator(func):
4 @wraps(func) # 保留原函数的 __name__ 和文档
5 def wrapper(*args, **kwargs):
6 return func(*args, **kwargs)
7 return wrapper
8
9@my_decorator
10def test():
11 """我是测试函数"""
12
13print(test.__name__) # test(没有@wraps会是wrapper)

七、常见错误与解决

常见错误与解决

错误现象原因 / 解决方法
装饰器返回值不是函数装饰器内部必须 return 一个函数,否则调用方拿到的是 None。
wrapper 没 return 原函数结果带返回值的函数被装饰后变 None,记得 return result。
被装饰后函数信息丢失用 @wraps(func) 保留原名和文档。
✍️ 小练习
写一个装饰器 check_login,模拟登录校验(没登录打印"请先登录",否则执行原函数);再写一个缓存装饰器(简单版)。
📌 本节小结
装饰器 = 高阶函数:接收函数、返回增强版函数。@语法糖让代码干净,wrapper(*args,**kwargs) 是标准模板,@wraps 保留元信息。日志、计时、鉴权、缓存全靠它。