使用Python上下文管理器获取上下文内的函数调用顺序
这个需求完全可以实现。
你之前使用inspect.stack失败的核心原因是:inspect.stack只能获取调用时刻的当前调用栈信息,如果你是在上下文管理器的__exit__阶段才去查栈,此时上下文中前序执行的add、divide函数已经执行完成并退出调用栈,自然只能拿到最后执行的multiply的相关记录。
实现方案
方案1:轻量装饰器方案(无性能损耗,推荐)
如果允许对需要追踪的函数添加装饰器标记,这是最稳定易用的实现方式:
import functools # 用于标记需要追踪的函数的装饰器 def track_call(func): @functools.wraps(func) def wrapper(*args, **kwargs): # 当存在活跃的追踪上下文时记录调用 if getattr(CallTracker, "active_context", None) is not None: CallTracker.active_context.append((func, args)) return func(*args, **kwargs) return wrapper # 上下文管理器实现 class CallTracker: active_context = None def __enter__(self): self.record = [] CallTracker.active_context = self.record return self def __exit__(self, exc_type, exc_val, exc_tb): CallTracker.active_context = None
使用方式如下:
# 给需要追踪的函数加上装饰器 @track_call def add(a, b): return a + b @track_call def divide(a, b): return a / b @track_call def multiply(a, b): return a * b # 调用示例 with CallTracker() as tracker: add(1, 2) divide(3, 4) multiply(5, 6) print(tracker.record) # 输出格式与需求一致:[(<function add at 0x...>, (1, 2)), (<function divide at 0x...>, (3, 4)), (<function multiply at 0x...>, (5, 6))]
方案2:无侵入追踪方案(无需修改原有函数)
如果不想对原有函数做任何修改,可以临时启用Python的系统追踪接口sys.settrace实现调用记录,注意该方案会产生微小的性能损耗:
import sys import inspect class NoIntrudeCallTracker: def __enter__(self): self.record = [] self.context_frame = inspect.currentframe().f_back # 注册追踪函数 sys.settrace(self.trace_func) return self def trace_func(self, frame, event, arg): # 只捕获上下文范围内的顶层函数调用 if event == "call" and frame.f_back == self.context_frame: func_name = frame.f_code.co_name func = frame.f_globals.get(func_name, None) if func: args = tuple(frame.f_locals[name] for name in frame.f_code.co_varnames[:frame.f_code.co_argcount]) self.record.append((func, args)) return self.trace_func def __exit__(self, exc_type, exc_val, exc_tb): sys.settrace(None)
使用时直接包裹代码即可,无需修改原有函数定义:
with NoIntrudeCallTracker() as tracker: add(1, 2) divide(3, 4) multiply(5, 6) print(tracker.record)
内容的提问来源于stack exchange,提问作者marwan
相关产品推荐
相关产品推荐

