题目
实现带参数的装饰器用于函数执行时间统计并支持自定义时间单位
信息
- 类型:问答
- 难度:⭐⭐
考点
装饰器定义与使用, 带参数装饰器的实现, 时间处理, 函数包装
快速回答
实现要点:
- 使用两层嵌套函数实现带参装饰器
- 内层装饰器使用
functools.wraps保留元数据 - 通过
time.perf_counter()精确计时 - 根据单位参数动态转换时间单位
- 输出格式:
函数名 - 执行时间[单位]
问题背景
在性能优化场景中,需要测量函数执行时间并支持灵活切换时间单位(秒/毫秒/微秒)。要求实现一个带参数的装饰器@timeit(unit='ms'),满足:
- 统计被装饰函数的执行时间
- 支持单位参数:'s'(秒)、'ms'(毫秒)、'us'(微秒)
- 输出格式:
函数名 executed in 时间值[单位]
解决方案
import time
import functools
def timeit(unit='s'):
"""带参数的时间统计装饰器"""
# 参数验证
valid_units = {'s', 'ms', 'us'}
if unit not in valid_units:
raise ValueError(f"Invalid unit. Use one of {valid_units}")
# 单位转换因子
multipliers = {'s': 1, 'ms': 1000, 'us': 1000000}
def decorator(func):
@functools.wraps(func) # 保留函数元数据
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
# 计算并转换时间
duration = (end - start) * multipliers[unit]
print(f"{func.__name__} executed in {duration:.6f}{unit}")
return result
return wrapper
return decorator
# 使用示例
@timeit(unit='ms')
def calculate_sum(n):
return sum(range(n))
calculate_sum(1000000) # 输出: calculate_sum executed in 25.456789ms核心原理
- 三层结构:装饰器工厂(
timeit)→ 装饰器(decorator)→ 包装函数(wrapper) - 闭包特性:内层函数记住外层作用域(
unit和multipliers) - 时间测量:
time.perf_counter()提供高精度计时(优于time.time())
最佳实践
- 使用
functools.wraps保留函数名、文档等元数据 - 用字典存储单位转换因子避免分支判断
- 格式化输出保留6位小数平衡精度与可读性
- 对装饰器参数进行有效性校验
常见错误
- 错误1:忘记使用
@functools.wraps导致被装饰函数元数据丢失 - 错误2:在装饰器工厂内直接实现包装逻辑(缺少
decorator层) - 错误3:使用
time.time()导致计时精度不足 - 错误4:未处理单位参数大小写(应转换为小写或规范输入)
扩展知识
- 类装饰器:通过实现
__call__方法可达到相同效果 - 异步支持:用
async def和await可扩展为异步函数计时器 - 日志集成:将
print替换为logging模块实现生产级日志 - 性能分析:结合
cProfile可进行更深入的性能诊断