Python案例:如何自定义装饰器?从零到精通的实战指南
📚 目录导读
- 什么是装饰器?为什么需要它?
- Python装饰器的核心原理
- 手把手:第一个自定义装饰器
- 进阶:带参数的装饰器
- 实战案例:日志、性能测试、权限校验
- 常见踩坑与解决方案
- 问答环节:你关心的问题都在这
什么是装饰器?为什么需要它?
装饰器(Decorator) 是Python中一种特殊的函数,它允许你在不修改原函数代码的前提下,为函数增加额外的功能,装饰器就像给函数“套上一层包装”——比如给一个普通函数穿上“日志外衣”或“性能监控夹克”。

典型场景:
- 记录函数调用日志
- 计算函数执行时间
- 权限验证(如检查用户是否登录)
- 缓存函数结果
- 异常重试机制
问:装饰器和普通函数调用有什么区别?
答:装饰器是声明式的——你只需在函数定义前加一行@装饰器名,后续调用时自动增强,而普通函数调用需要你手动在每处调用前后写额外代码,导致重复、难以维护。
Python装饰器的核心原理
装饰器的本质是 高阶函数(接受一个函数作为参数,并返回一个新函数),其底层原理可用以下代码表示:
def decorator(func):
def wrapper(*args, **kwargs):
# 增强功能
return func(*args, **kwargs)
return wrapper
# 使用装饰器
@decorator
def say_hello():
print("Hello!")
# 等价于:
say_hello = decorator(say_hello)
关键点:
wrapper函数接收原函数的所有参数(*args, **kwargs)wrapper内部调用原函数并返回结果- 装饰器返回
wrapper对象,替换原函数
问:为什么
wrapper中的*args, **kwargs是必须的?
答:原函数可能有不同数量的参数,使用通用写法可以让装饰器适配任何函数,否则参数不匹配时会报错。
手把手:第一个自定义装饰器
案例:简单的“执行时间统计”装饰器
import time
def timer_decorator(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"⏱️ {func.__name__} 执行耗时:{end - start:.4f}秒")
return result
return wrapper
@timer_decorator
def compute_sum(n):
total = sum(range(n))
return total
# 调用
result = compute_sum(1000000)
print(f"结果:{result}")
输出示例:
⏱️ compute_sum 执行耗时:0.0321秒
结果:499999500000
优化提示:使用functools.wraps保留原函数元信息(如__name__、__doc__):
from functools import wraps
def timer_decorator(func):
@wraps(func) # 关键!保留原函数信息
def wrapper(*args, **kwargs):
...
return wrapper
问:不加
@wraps会怎样?
答:被装饰后,compute_sum.__name__会变成wrapper,导致调试混乱,@wraps能修复此问题。
进阶:带参数的装饰器
有时我们需要给装饰器传递参数(如日志级别、重试次数),这需要额外一层嵌套:
def retry_decorator(max_attempts=3, delay=1):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"尝试第{attempt}次失败:{e}")
if attempt == max_attempts:
raise
time.sleep(delay)
return wrapper
return decorator
@retry_decorator(max_attempts=5, delay=2)
def unstable_api():
import random
if random.random() < 0.7:
raise ConnectionError("网络波动")
return "成功"
print(unstable_api())
解析:三层结构:
retry_decorator(max_attempts, delay)返回decoratordecorator(func)返回wrapperwrapper内部执行重试逻辑
问:如何让装饰器支持
@装饰器和@装饰器(参数)两种写法?
答:可以使用functools.partial判断参数类型,但更简单的是统一用带括号的写法,即@装饰器()。
实战案例:日志、性能测试、权限校验
案例1:自动记录函数调用日志
import logging
def log_decorator(level=logging.INFO):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
logger = logging.getLogger(func.__module__)
logger.log(level, f"调用 {func.__name__},参数:{args}, {kwargs}")
result = func(*args, **kwargs)
logger.log(level, f"{func.__name__} 返回:{result}")
return result
return wrapper
return decorator
@log_decorator()
def divide(a, b):
return a / b
divide(10, 2) # 自动输出日志
案例2:类方法装饰器(权限校验)
def require_role(role):
def decorator(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
if not hasattr(self, 'user_role') or self.user_role != role:
raise PermissionError("权限不足")
return func(self, *args, **kwargs)
return wrapper
return decorator
class AdminAPI:
def __init__(self, user_role):
self.user_role = user_role
@require_role('admin')
def delete_user(self, user_id):
print(f"删除用户 {user_id}")
api = AdminAPI('user')
api.delete_user(123) # 抛出权限错误
问:装饰器能作用于类而不是方法吗?
答:可以,类装饰器接受类作为参数,返回修改后的类(如添加属性),原理与函数装饰器一致。
常见踩坑与解决方案
| 问题 | 表现 | 解决 |
|---|---|---|
| 丢失元信息 | func.__name__变成wrapper |
添加@wraps(func) |
| 装饰器顺序问题 | 多层装饰器执行顺序混乱 | 从上到下装饰,执行时从下到上 |
| 参数不兼容 | 原函数有默认参数时出错 | 确保wrapper使用*args, **kwargs |
类中self丢失 |
被装饰方法调用时self为空 |
检查wrapper是否传递了self |
| 装饰器内部异常 | 异常未正确处理 | 使用try/except包裹原函数调用 |
多层装饰器示例:
@decorator_A @decorator_B def func(): ... # 等效于:func = decorator_A(decorator_B(func))
执行时先执行最外层装饰器(A)的额外代码,然后进入B的额外代码,最后执行原函数。
问答环节:你关心的问题都在这
Q1:装饰器可以修改函数签名吗?
A:技术上可以,但强烈不推荐,修改签名会导致IDE提示不准确、文档混乱,建议保持原函数行为不变,只增加额外功能。
Q2:如何调试被装饰的函数?
A:使用@wraps保留原信息,或者直接print(被装饰函数.__wrapped__)获取原始函数。
Q3:装饰器适合用在哪些生产场景?
A:日志记录、性能监控、缓存(如functools.lru_cache)、重试机制、数据库连接管理、事务控制、权限校验等,大型框架如Flask、Django的@app.route、@login_required本质上都是装饰器。
Q4:有没有性能问题?
A:每次调用都会经过wrapper,但额外开销通常微乎其微(微秒级别),如果极端注重性能(如每秒百万次调用),可考虑用functools.partial替代。
Q5:如何查看一个函数被哪些装饰器装饰过?
A:递归检查__wrapped__属性链:
def unwrap_func(func):
while hasattr(func, '__wrapped__'):
func = func.__wrapped__
return func
自定义装饰器是Python高级特性中最实用的技能之一,掌握它,你将能够:
- ✅ 用几行代码替代重复的增强逻辑
- ✅ 让代码更模块化、更易维护
- ✅ 深入理解“函数即对象”的Python哲学
建议练习路径:从简单的日志装饰器开始 → 尝试带参数的重试装饰器 → 结合类编写权限装饰器 → 阅读Flask源码中的装饰器实现,每多写一个装饰器,你对Python的理解就会加深一层。