You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何实现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

火山引擎 最新活动