在Python中改进堆栈追踪钩子

3 投票
1 回答
863 浏览
提问于 2025-04-16 08:13

大家好,

我有一个问题,跟这个问题2617120有点像,可以在这里找到:

  • 如何使用traceit来报告函数输入变量
  • 提问者想知道如何让Python在通过跟踪钩子执行时打印出函数的参数。

    我想要的东西跟这个很相似,但有点不同。我希望在代码运行时对其进行eval(执行),并打印出任何被执行的变量。例如,使用以下代码:

    for modname in modnames:                   
    
    if not modname or '.' in modname:      
         continue                                                                    
    ...                                  
    

    这个跟踪钩子会导致以下内容被打印出来:

    for modname in modnames:                | for init in init,., encoding
                                            |
    if not modname or '.' in modname:       | if not init or '.' in init
         continue                           |     continue
    if not modname or '.' in modname:       | if not . or '.' in .
    ...                                     |
    

    这里的代码行会根据当前的运行环境进行插值。我在Perl中做过这个,在某些情况下真的是个救星。

    有没有人知道在Python中实现这个的最佳方法?我有一些想法,但我想听听大家的看法(如果有人有现成的解决方案也可以分享)

    顺便说一下,这里是参考代码:

    import sys
    import linecache
    import random
    
    def traceit(frame, event, arg):
        if event == "line":
            lineno = frame.f_lineno
            filename = frame.f_globals["__file__"]
            if filename == "<stdin>":
                filename = "traceit.py"
            if (filename.endswith(".pyc") or
                filename.endswith(".pyo")):
                filename = filename[:-1]
            name = frame.f_globals["__name__"]
            line = linecache.getline(filename, lineno)
            print "%s:%s:%s: %s" % (name,  lineno,frame.f_code.co_name,line.rstrip())
        return traceit
    
    
    def main():
        print "In main"
        for i in range(5):
            print i, random.randrange(0, 10)
        print "Done."
    
    sys.settrace(traceit)
    main()
    

    1 个回答

    0

    这里有个简单的小技巧,可以帮助你开始处理一行文本,假设这行文本存储在 line 里,而当前的堆栈框架在 frame 中(顺便说一下,这个是 traceit 函数的内部功能)。

    import re
    from types import *
    
    def interpolatevar(matchobj):
        excludetypes = set((NoneType, TypeType, FunctionType, LambdaType, ClassType,
                        CodeType, InstanceType, MethodType, BuiltinFunctionType,
                        BuiltinMethodType))
    
        var = matchobj.group(0)
        basevar = var.split(".")[0]
        if basevar in frame.f_code.co_names or basevar in frame.f_code.co_varnames:
            if basevar in frame.f_globals or basevar in frame.f_locals:
                val = eval(var, frame.f_globals, frame.f_locals)
                if type(val) not in excludetypes:
                    return repr(val)
        return var
    
    line = re.sub(r"[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*", 
                  interpolatevar, line)
    

    这个方法使用正则表达式来查找标识符,所以即使它们在字符串中,也会被愚蠢地找到。不过,标识符必须在函数中实际使用,并且在局部或全局范围内定义。

    它可以处理对象属性的访问(比如 foo.bar 会被替换成对应的值),而我最初发布的版本是做不到这一点的。同时,它会过滤掉各种类型的值,因为把 foo 替换成 <function foo at 0x02793470> 并不会告诉你什么新信息。(当然,如果你对某些类型的值感兴趣,排除列表是可以轻松自定义的,或者你也可以从 types 模块中添加其他类型。)

    从长远来看,我觉得查看这行代码的字节码可能会更有帮助,因为那样更容易识别哪些标记实际上是标识符。ast 模块也可能有用,它可以生成语句的解析树,这样你就能搞清楚标识符是什么,但在只看到一行代码的情况下,这对条件语句和循环来说会有些麻烦。

    撰写回答