

大家好,我是正在实战各种AI项目的程序员晚枫。
今天聊一个让Python代码瞬间变高级的特性——装饰器(Decorator)。
第一次听说这个词时,我以为是什么高深的东西。后来才发现,它就像给手机贴膜一样简单:在不改变原函数的情况下,给它加上新功能。
而且,面试必问。
看完这篇文章,你也能写出带@符号的"高端代码"。
从一个真实需求开始
假设你写了10个函数,老板说:"给每个函数加个计时功能,我要知道每个函数跑了多久。"
❌ 笨方法:每个函数都手动加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import time
def func1(): start = time.time() time.sleep(0.1) print(f"func1 耗时:{time.time() - start}秒")
def func2(): start = time.time() time.sleep(0.2) print(f"func2 耗时:{time.time() - start}秒")
def func3(): start = time.time() time.sleep(0.15) print(f"func3 耗时:{time.time() - start}秒")
|
缺点:
- 代码重复
- 维护困难
- 改一个漏一个
- 违反DRY原则(Don't Repeat Yourself)
✅ 聪明方法:用装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import time
def timer(func): def wrapper(): start = time.time() result = func() elapsed = time.time() - start print(f"{func.__name__} 耗时:{elapsed:.4f}秒") return result return wrapper
@timer def func1(): time.sleep(0.1) return "func1完成"
@timer def func2(): time.sleep(0.2) return "func2完成"
@timer def func3(): time.sleep(0.15) return "func3完成"
func1() func2() func3()
|
运行结果:
1 2 3
| func1 耗时:0.1002秒 func2 耗时:0.2001秒 func3 耗时:0.1503秒
|
你只加了一个 @,每个函数就自动拥有了计时功能!
装饰器原理:其实就是一个函数
不要被 @ 符号吓到,装饰器的本质就是一个接收函数作为参数,返回新函数的函数。
拆解装饰器的执行过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| def my_decorator(func): """这是一个装饰器""" def wrapper(): print("🔔 函数执行前") func() print("🔔 函数执行后") return wrapper
def say_hello(): print("Hello!")
say_hello()
print("--- 分割线 ---")
@my_decorator def say_goodbye(): print("Goodbye!")
say_goodbye()
|
输出:
1 2 3 4 5 6 7
| 🔔 函数执行前 Hello! 🔔 函数执行后 --- 分割线 --- 🔔 函数执行前 Goodbye! 🔔 函数执行后
|
@ 就是语法糖,让代码更优雅!
图解装饰器原理
1 2 3 4 5 6 7 8 9 10
| 原始函数:say_hello ↓ 装饰器 my_decorator(say_hello) ↓ 新函数 wrapper(包含前后增强) ↓ say_hello 变量指向 wrapper
再次调用 say_hello() 时, 实际调用的是 wrapper()
|
带参数的装饰器(必须掌握)
上面的装饰器有个问题:如果原函数有参数怎么办?
1 2 3 4 5 6 7 8 9 10 11
| def timer(func): def wrapper(): start = time.time() func() print(f"耗时:{time.time() - start}秒") return wrapper
@timer def greet(name): print(f"Hello, {name}!")
|
✅ 万能参数版装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import time
def timer(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) elapsed = time.time() - start print(f"{func.__name__} 耗时:{elapsed:.4f}秒") return result return wrapper
@timer def slow_add(a, b): time.sleep(1) return a + b
@timer def slow_greet(name, greeting="Hello"): time.sleep(0.5) return f"{greeting}, {name}!"
@timer def slow_calc(n, **options): time.sleep(0.3) return sum(range(n)) * options.get('multiplier', 1)
print(slow_add(1, 2)) print(slow_greet("程序员晚枫", greeting="你好")) print(slow_calc(100, multiplier=2))
|
输出:
1 2 3 4 5 6
| slow_add 耗时:1.0012秒 3 slow_greet 耗时:0.5003秒 你好, 程序员晚枫! slow_calc 耗时:0.3002秒 4950
|
🎯 记住这个万能模板:
1 2 3 4 5 6 7
| def my_decorator(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) return result return wrapper
|
带参数的装饰器(双层嵌套)
有时候你需要在装饰器上传参数,比如控制重试次数、日志级别等。
场景:可配置的重试装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import time import random
def retry(max_attempts=3, delay=1): """ 重试装饰器工厂 - max_attempts: 最大重试次数 - delay: 重试间隔(秒) """ def decorator(func): def wrapper(*args, **kwargs): for attempt in range(max_attempts): try: return func(*args, **kwargs) except Exception as e: if attempt < max_attempts - 1: print(f"⚠️ 第{attempt+1}次失败,{delay}秒后重试...") time.sleep(delay) else: print(f"❌ 最终失败:{e}") raise return wrapper return decorator
@retry(max_attempts=5, delay=0.5) def fetch_data(): """模拟可能失败的网络请求""" if random.random() < 0.7: raise ConnectionError("网络连接失败") return "数据获取成功!"
try: result = fetch_data() print(result) except: print("请求失败,已重试5次")
|
理解三层嵌套
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| def outer(参数): def middle(func): def inner(*args, **kwargs): result = func(*args, **kwargs) return result return inner return middle
@outer(参数) def target(): pass
target = outer(参数)(target)
|
装饰器有个副作用:会覆盖原函数的 __name__、__doc__ 等元信息。
1 2 3 4 5 6 7 8 9 10 11 12 13
| def my_decorator(func): def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper
@my_decorator def say_hello(): """这是 say_hello 函数的文档""" print("Hello!")
print(say_hello.__name__) print(say_hello.__doc__)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| from functools import wraps
def my_decorator(func): @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper
@my_decorator def say_hello(): """这是 say_hello 函数的文档""" print("Hello!")
print(say_hello.__name__) print(say_hello.__doc__)
|
@wraps 的作用:把原函数的 __name__、__doc__、__annotations__ 等信息复制到 wrapper 上。
常用装饰器示例(收藏备用)
1. 计时装饰器(完整版)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import time from functools import wraps
def timer(func): """记录函数执行时间""" @wraps(func) def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) elapsed = time.time() - start print(f"⏱️ {func.__name__} 执行耗时:{elapsed:.4f}秒") return result return wrapper
@timer def process_data(n): """模拟数据处理""" return sum(range(n))
print(process_data(100000))
|
2. 权限检查装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| from functools import wraps
def require_login(func): """检查用户是否登录""" @wraps(func) def wrapper(user, *args, **kwargs): if not getattr(user, 'is_logged_in', False): raise PermissionError("请先登录!") return func(user, *args, **kwargs) return wrapper
def require_admin(func): """检查用户是否是管理员""" @wraps(func) def wrapper(user, *args, **kwargs): if not getattr(user, 'is_admin', False): raise PermissionError("需要管理员权限!") return func(user, *args, **kwargs) return wrapper
class User: def __init__(self, name, is_logged_in=True, is_admin=False): self.name = name self.is_logged_in = is_logged_in self.is_admin = is_admin
@require_login def view_dashboard(user): return f"{user.name} 的仪表盘"
@require_admin def delete_user(user, user_id): return f"{user.name} 删除了用户 {user_id}"
guest = User("游客", is_logged_in=False) normal_user = User("张三", is_logged_in=True, is_admin=False) admin = User("管理员", is_logged_in=True, is_admin=True)
try: print(view_dashboard(guest)) except PermissionError as e: print(f"❌ {e}")
print(view_dashboard(normal_user)) print(delete_user(admin, 123))
|
3. 缓存装饰器(记忆化)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| from functools import wraps
def memoize(func): """缓存函数结果,避免重复计算""" cache = {} @wraps(func) def wrapper(*args, **kwargs): key = (args, tuple(sorted(kwargs.items()))) if key not in cache: cache[key] = func(*args, **kwargs) print(f"📦 缓存 miss:{func.__name__}{key}") else: print(f"📦 缓存 hit:{func.__name__}{key}") return cache[key] return wrapper
@memoize def fibonacci(n): """斐波那契数列""" if n < 2: return n return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10)) print(fibonacci(10))
|
4. 日志装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import logging from functools import wraps
logging.basicConfig(level=logging.INFO, format='%(message)s') logger = logging.getLogger(__name__)
def log(func): """记录函数调用日志""" @wraps(func) def wrapper(*args, **kwargs): logger.info(f"📥 调用 {func.__name__}({args}, {kwargs})") result = func(*args, **kwargs) logger.info(f"📤 返回 {func.__name__} -> {result}") return result return wrapper
@log def add(a, b): return a + b
@log def process(name, times=1): return f"处理 {name} {times} 次"
print(add(1, 2)) print(process("数据"))
|
5. 重试装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import time from functools import wraps
def retry(max_attempts=3, delay=1, exceptions=(Exception,)): """失败时自动重试的装饰器""" def decorator(func): @wraps(func) def wrapper(*args, **kwargs): last_exception = None for attempt in range(max_attempts): try: return func(*args, **kwargs) except exceptions as e: last_exception = e if attempt < max_attempts - 1: print(f"⚠️ 第{attempt+1}次失败:{e},{delay}秒后重试...") time.sleep(delay) raise last_exception return wrapper return decorator
@retry(max_attempts=3, delay=0.5) def call_api(endpoint): """模拟API调用""" import random if random.random() < 0.8: raise TimeoutError("请求超时") return f"API响应:{endpoint}"
for i in range(3): print(f"\n=== 第{i+1}次调用 ===") try: print(call_api("/users")) except TimeoutError as e: print(f"❌ 彻底失败:{e}")
|
6. 单例装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| from functools import wraps
def singleton(cls): """确保类只有一个实例""" instances = {} @wraps(cls) def wrapper(*args, **kwargs): if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return wrapper
@singleton class Database: def __init__(self): print("🔄 创建数据库连接") def query(self, sql): return f"执行:{sql}"
db1 = Database() db2 = Database() print(db1 is db2) db1.query("SELECT * FROM users")
|
多个装饰器叠加
可以给一个函数加多个装饰器,它们的执行顺序是从下往上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| def decorator_a(func): def wrapper(*args, **kwargs): print("A 开始") result = func(*args, **kwargs) print("A 结束") return result return wrapper
def decorator_b(func): def wrapper(*args, **kwargs): print("B 开始") result = func(*args, **kwargs) print("B 结束") return result return wrapper
@decorator_a @decorator_b def my_func(): print("my_func 执行中")
my_func()
|
输出:
1 2 3 4 5
| A 开始 B 开始 my_func 执行中 B 结束 A 结束
|
等价于:
1
| my_func = decorator_a(decorator_b(my_func))
|
执行顺序: 从下往上(B先执行)→ 从上往下(A先结束)
类装饰器:装饰器的高级玩法
装饰器不一定是函数,类也可以做装饰器!
场景:给函数加上调用计数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class Counter: """统计函数被调用了多少次""" def __init__(self, func): self.func = func self.count = 0 self.__name__ = func.__name__ self.__doc__ = func.__doc__ def __call__(self, *args, **kwargs): self.count += 1 print(f"📊 {self.__name__} 被调用了 {self.count} 次") return self.func(*args, **kwargs)
@Counter def say_hello(): return "Hello!"
@Counter def add(a, b): return a + b
say_hello() say_hello() say_hello() print(add(1, 2)) print(add(10, 20))
|
类装饰器 vs 函数装饰器
| 特性 | 函数装饰器 | 类装饰器 |
|---|
| 适用场景 | 简单增强 | 需要维护状态 |
| 状态保持 | 需要闭包 | 可以用实例属性 |
| 代码复杂度 | 较简单 | 较复杂 |
避坑指南:装饰器最容易踩的6个坑
坑1:装饰器参数顺序写反
1 2 3 4 5 6 7 8 9 10
|
@retry(max_attempts=3) def api_call(): pass
|
坑2:忘记用 @wraps 导致调试困难
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| from functools import wraps
def bad_decorator(func): def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper
@bad_decorator def greet(): """打招呼函数""" pass
print(greet.__name__)
def good_decorator(func): @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper
@good_decorator def greet2(): """打招呼函数""" pass
print(greet2.__name__)
|
坑3:装饰器改变了返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| def bad_timer(func): def wrapper(*args, **kwargs): start = time.time() func(*args, **kwargs) print(f"耗时:{time.time() - start}秒") return wrapper
@bad_timer def add(a, b): return a + b
result = add(1, 2) print(result)
def good_timer(func): @wraps(func) def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) print(f"耗时:{time.time() - start}秒") return result return wrapper
|
坑4:装饰器改变了异常行为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| def bad_handler(func): def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except: pass return wrapper
@bad_handler def divide(a, b): return a / b
result = divide(1, 0)
def good_handler(func): def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except Exception as e: print(f"处理异常:{e}") raise return wrapper
|
坑5:在装饰器中使用了可变默认参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| def bad_cache(func): cache = [] def wrapper(*args): if args in cache: print("命中缓存") return result = func(*args) cache.append(args) return result return wrapper
def good_cache(func): _cache = {} def wrapper(*args): if args in _cache: print("命中缓存") return _cache[args] result = func(*args) _cache[args] = result return result return wrapper
|
坑6:装饰器顺序影响结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| def tag(tag_name): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): return f"<{tag_name}>{func(*args, **kwargs)}</{tag_name}>" return wrapper return decorator
@tag("b") @tag("i") def text(): return "Hello"
print(text())
|
实战案例:Flask路由装饰器原理
Flask 之所以简洁,就是用了装饰器。让我们仿写一个简化版:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| from functools import wraps
routes = {}
def route(path): """Flask风格路由装饰器""" def decorator(func): @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) routes[path] = func return wrapper return decorator
@route("/") def index(): return "首页"
@route("/about") def about(): return "关于我们"
@route("/user/<name>") def user(name): return f"用户:{name}"
print("注册的路由:") for path, func in routes.items(): print(f" {path} -> {func.__name__}")
def simulate_request(path): if path in routes: return routes[path]() return "404 Not Found"
print(simulate_request("/")) print(simulate_request("/about")) print(simulate_request("/user/程序员晚枫"))
|
推荐:AI Python零基础实战营
想深入学习Python高级特性,写出面试官眼前一亮的代码?
课程内容:
- ✅ Python基础语法
- ✅ 函数与面向对象
- ✅ 装饰器、生成器、上下文管理器
- ✅ 实战项目练习
🎁 限时福利:送《Python编程从入门到实践》实体书
👉 点击了解详情
本讲小结
| 概念 | 说明 |
|---|
| 装饰器本质 | 接收函数,返回新函数 |
| @语法糖 | func = decorator(func) 的简写 |
| 万能参数 | def wrapper(*args, **kwargs) |
| 保留元信息 | @wraps(func) |
| 多装饰器 | 从下往上执行 |
💡 记住核心:不修改原函数,给它加功能。核心模板只有3行!
相关阅读
PS:装饰器是Python的"黑魔法"之一,掌握它,你的代码会简洁又强大。记住核心:不修改原函数,给它加功能。
📚 推荐教材
主教材:《Python 编程从入门到实践(第 3 版)》
📚 推荐:Python 零基础实战营
系统学习Python,推荐这个免费入门课程 👇
| 特点 | 说明 |
|---|
| 🎯 专为0基础设计 | 门槛低,上手快 |
| 📹 配套视频讲解 | 配合文章学习效果更好 |
| 💬 专属答疑群 | 遇到问题有人带 |
| 🎁 实体书赠送 | 优秀学员送《Python编程从入门到实践》 |
👉 点击免费领取 Python 零基础实战营
💬 联系我
主营业务:AI 编程培训、企业内训、技术咨询
🎓 AI 编程实战课程
想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!