1. Python装饰器基础与@mylog.timer实现原理
装饰器是Python中一种强大的语法特性,它允许在不修改原函数代码的情况下,为函数添加额外的功能。@mylog.timer装饰器就是这种特性的典型应用,专门用于测量和记录函数执行时间。
1.1 装饰器核心工作机制
装饰器本质上是一个高阶函数,它接收一个函数作为参数,并返回一个新的函数。当使用@语法应用装饰器时,Python解释器会自动执行以下操作:
python复制@decorator
def func():
pass
# 等价于
func = decorator(func)
这种机制使得我们可以在不改变原函数定义的情况下,动态地修改函数的行为。对于计时场景,装饰器特别适合,因为它可以无侵入地为任何函数添加计时功能。
1.2 @mylog.timer的典型实现
一个基础的计时装饰器实现通常包含以下要素:
python复制import time
from functools import wraps
def timer(func):
@wraps(func) # 保留原函数的元信息
def wrapper(*args, **kwargs):
start_time = time.perf_counter() # 高精度计时开始
result = func(*args, **kwargs) # 执行原函数
end_time = time.perf_counter() # 计时结束
print(f"{func.__name__} executed in {end_time - start_time:.4f} seconds")
return result
return wrapper
这个基础版本已经可以实现函数计时功能,但@mylog.timer通常会在此基础上进行更多增强:
- 日志记录而非简单打印
- 多级时间单位自动适配(ns/ms/s)
- 调用参数记录
- 异常处理和时间统计
1.3 为什么选择装饰器实现计时
相比手动在函数前后添加计时代码,装饰器方案具有明显优势:
- 非侵入性:不修改原函数逻辑
- 复用性:一次实现,多处使用
- 一致性:所有被装饰函数采用相同计时标准
- 可维护性:计时逻辑集中管理
提示:使用functools.wraps保留原函数元信息是装饰器最佳实践,否则被装饰函数会丢失__name__等属性,影响调试和文档生成。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 高级@mylog.timer实现与优化
基础计时器只能满足简单需求,生产环境中我们需要更健壮、灵活的解决方案。下面逐步构建一个工业级的@mylog.timer实现。
2.1 带日志记录功能的计时器
首先整合Python标准库的logging模块:
python复制import logging
from functools import wraps
logger = logging.getLogger('performance')
def timer(level=logging.DEBUG):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter_ns()
try:
result = func(*args, **kwargs)
elapsed = (time.perf_counter_ns() - start) / 1e9
logger.log(level,
f"{func.__module__}.{func.__name__} "
f"took {elapsed:.6f}s")
return result
except Exception as e:
elapsed = (time.perf_counter_ns() - start) / 1e9
logger.log(level,
f"{func.__module__}.{func.__name__} "
f"failed after {elapsed:.6f}s: {str(e)}")
raise
return wrapper
return decorator
这个版本新增了:
- 可配置的日志级别
- 模块名+函数名的完整标识
- 异常情况下的计时记录
- 纳秒级计时精度
2.2 自动时间单位转换
对于不同尺度的执行时间,自动选择合适单位会更友好:
python复制def format_time(seconds):
if seconds < 1e-6:
return f"{seconds * 1e9:.2f}ns"
elif seconds < 1e-3:
return f"{seconds * 1e6:.2f}μs"
elif seconds < 1:
return f"{seconds * 1e3:.2f}ms"
else:
return f"{seconds:.2f}s"
在wrapper中调用:
python复制elapsed = time.perf_counter() - start
logger.log(level, f"Execution time: {format_time(elapsed)}")
2.3 上下文信息增强
记录调用参数可以更好地分析性能瓶颈:
python复制def wrapper(*args, **kwargs):
arg_str = ", ".join([repr(a) for a in args])
kwarg_str = ", ".join(f"{k}={repr(v)}" for k,v in kwargs.items())
call_info = f"{func.__name__}({arg_str}{', ' if args and kwargs else ''}{kwarg_str})"
start = time.perf_counter()
try:
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
logger.info(f"{call_info} -> {elapsed:.6f}s")
return result
except Exception as e:
elapsed = time.perf_counter() - start
logger.error(f"{call_info} failed after {elapsed:.6f}s: {str(e)}")
raise
3. @mylog.timer的生产环境应用
3.1 与日志系统集成
在实际项目中,@mylog.timer通常会与现有日志系统深度集成:
python复制class PerfLogger:
def __init__(self, name='perf', level=logging.INFO):
self.logger = logging.getLogger(name)
self.level = level
def timer(self, func=None, *, name=None):
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
start = time.perf_counter()
try:
result = f(*args, **kwargs)
elapsed = time.perf_counter() - start
self.logger.log(
self.level,
f"{name or f.__name__} | {elapsed:.6f}s",
extra={'elapsed': elapsed}
)
return result
except Exception as e:
elapsed = time.perf_counter() - start
self.logger.error(
f"{name or f.__name__} failed after {elapsed:.6f}s",
exc_info=True
)
raise
return wrapper
if func is None:
return decorator
return decorator(func)
# 使用示例
mylog = PerfLogger()
@mylog.timer(name='data_processing')
def process_data():
pass
这种实现允许:
- 自定义日志名称
- 统一日志级别配置
- 可选的重命名计时任务
- 结构化日志记录(elapsed作为额外字段)
3.2 性能统计与聚合
对于需要长期监控的函数,可以添加统计功能:
python复制from collections import defaultdict
import statistics
class StatsTimer:
def __init__(self):
self.stats = defaultdict(list)
def timer(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
try:
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
self.stats[func.__name__].append(elapsed)
return result
except Exception:
elapsed = time.perf_counter() - start
self.stats[f"{func.__name__}_error"].append(elapsed)
raise
return wrapper
def get_stats(self):
return {
name: {
'count': len(times),
'total': sum(times),
'avg': statistics.mean(times),
'max': max(times),
'min': min(times),
'p90': statistics.quantiles(times, n=10)[-1]
}
for name, times in self.stats.items()
}
3.3 异步函数支持
现代Python项目中大量使用async/await,装饰器也需要相应适配:
python复制import asyncio
def async_timer(func):
@wraps(func)
async def wrapper(*args, **kwargs):
start = time.perf_counter()
try:
result = await func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"Async {func.__name__} took {elapsed:.4f}s")
return result
except Exception as e:
elapsed = time.perf_counter() - start
print(f"Async {func.__name__} failed after {elapsed:.4f}s: {e}")
raise
return wrapper
4. 实战技巧与性能考量
4.1 装饰器叠加顺序
当多个装饰器叠加使用时,执行顺序很重要:
python复制@decorator1
@decorator2
def func():
pass
# 等价于
func = decorator1(decorator2(func))
对于@mylog.timer,通常应该放在最外层,这样才能测量到所有其他装饰器的执行时间:
python复制@timer # 这个会最后执行,最先开始计时
@cache
@validate
def expensive_operation():
pass
4.2 计时开销控制
装饰器本身会引入额外开销,对于纳秒级操作,这个开销可能不可忽略:
python复制# 测试装饰器开销
def noop(): pass
@timer
def timed_noop(): pass
baseline = timeit.timeit(noop, number=100000)
overhead = timeit.timeit(timed_noop, number=100000) - baseline
print(f"Decorator overhead per call: {overhead / 100000 * 1e9:.2f}ns")
如果发现装饰器开销过大,可以考虑:
- 使用time.perf_counter_ns()替代time.perf_counter()
- 减少装饰器内部的逻辑复杂度
- 对于极端性能敏感的代码,可以提供非装饰器版本
4.3 常见问题排查
-
函数元信息丢失
- 现象:help(func)显示不正确
- 解决:确保使用@wraps(func)
-
异步函数不生效
- 现象:async函数计时不准
- 解决:使用专门的async_timer装饰器
-
日志重复输出
- 现象:同一条日志多次打印
- 解决:检查logging配置,避免重复添加handler
-
时间单位混淆
- 现象:1.23是秒还是毫秒不明确
- 解决:使用format_time统一格式化
-
装饰器参数传递错误
- 现象:@timer()漏写括号
- 解决:区分带参数和不带参数的装饰器写法
4.4 性能优化实践
对于高频调用的函数,可以添加阈值过滤:
python复制def timer(threshold=0.1):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
if elapsed >= threshold:
print(f"Slow call: {func.__name__} took {elapsed:.2f}s")
return result
return wrapper
return decorator
或者采用采样方式减少开销:
python复制import random
def sample_timer(rate=0.1):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
if random.random() < rate:
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"Sampled: {func.__name__} took {elapsed:.4f}s")
return result
return func(*args, **kwargs)
return wrapper
return decorator
在实现@mylog.timer时,我发现在长时间运行的服务中,最有用的是能够将计时数据与请求ID、上下文信息关联起来。这需要装饰器能够访问到线程局部存储或上下文变量,这也是为什么许多框架如Flask、Django都提供了自己的性能监控工具集成。
