如何实现print_call_stack_decorator装饰器,为调用栈中所有函数自动添加上下文管理输出
如何实现print_call_stack_decorator装饰器,为调用栈中所有函数自动添加上下文管理输出
嘿,这个需求挺实用的——只给最上层函数套一个装饰器,就能让整个调用栈里的所有函数都自动用上my_context的进入/退出日志,不用挨个给每个函数加装饰对吧?我之前做类似的调用栈追踪时,用Python的sys.settrace机制就能完美解决,给你一步步拆解实现:
首先,先理清楚核心思路:Python的sys.settrace可以帮我们监控代码执行时的各种事件(比如函数调用、返回、行执行等)。我们可以在装饰器里临时开启这个追踪功能,当被装饰的函数执行时,每进入一个Python函数就自动触发my_context的进入逻辑,函数执行完返回时再触发退出逻辑,这样整个调用栈的上下文嵌套就自动实现了。
接下来是完整的可运行代码,直接套你的现有代码就能用:
from contextlib import contextmanager import sys from types import FrameType @contextmanager def my_context(name): print(f"[ENTER] Context: {name}") try: yield finally: print(f"[EXIT] Context: {name}") def print_call_stack_decorator(func): def tracer(frame: FrameType, event: str, arg): # 只处理函数调用和返回的事件 if event == 'call': # 拿到当前被调用的函数名 func_name = frame.f_code.co_name # 手动触发上下文的进入操作 ctx = my_context(func_name) ctx.__enter__() # 把上下文对象存在当前函数的局部变量里,方便退出时用 frame.f_locals['_current_ctx'] = ctx elif event == 'return': # 取出之前存的上下文对象,触发退出操作 ctx = frame.f_locals.get('_current_ctx') if ctx: ctx.__exit__(None, None, None) # 返回tracer自身,继续追踪后续的执行事件 return tracer def wrapper(*args, **kwargs): # 先保存原来的全局追踪函数,避免影响其他代码 original_trace = sys.gettrace() try: # 开启我们的自定义追踪 sys.settrace(tracer) # 执行被装饰的最上层函数 result = func(*args, **kwargs) finally: # 不管函数执行成功还是报错,都要恢复原来的追踪设置 sys.settrace(original_trace) return result return wrapper # 你的原有函数不用做任何修改 def c(): print("c") def b(): c() print('b') @print_call_stack_decorator def a(): b() print('a') # 运行测试 a()
运行这段代码后,输出就完全是你想要的效果:
[ENTER] Context: a [ENTER] Context: b [ENTER] Context: c c [EXIT] Context: c b [EXIT] Context: b a [EXIT] Context: a
最后给你补几个要注意的细节:
- 这个追踪只会针对Python定义的函数,内置函数(比如
print)不会触发call事件,所以不会乱加上下文日志,刚好符合我们的需求。 - 用
finally块恢复原来的追踪函数是必须的——哪怕被装饰的函数执行时抛出异常,也不会把我们的追踪逻辑留在全局,避免影响其他代码的运行。 - 如果遇到递归函数,这个实现也能正确处理,因为每个递归调用都会生成独立的frame,上下文会按照递归的嵌套顺序正确进入和退出。
内容来源于stack exchange




