追踪文件路径和行号

3 投票
1 回答
1354 浏览
提问于 2025-04-17 14:23

我正在使用Python的trace模块来跟踪一些代码。当我这样跟踪代码时,我可以得到以下两种结果之一:

调用:

tracer = trace.Trace(count=False, trace=True, ignoredirs=[sys.prefix, sys.exec_prefix])
r = tracer.run('run()')
tracer.results().write_results(show_missing=True)

结果:

<filename>(<line number>): <line of code>

调用 [引用]:

tracer = trace.Trace(count=False, trace=True, ignoredirs=[sys.prefix, sys.exec_prefix], countfuncs=True)
r = tracer.run('run()')
tracer.results().write_results(show_missing=True)

结果:

filename:<filepath>, modulename:<module name>, funcname: <function name>

我真正需要的是一种跟踪方式,可以给我这样的输出:

<filepath> <line number>

看起来我可以利用上面的信息,把它们交错在一起,得到我需要的结果,但在以下情况下,这样的尝试会失败:

  • sys.path包含目录A和目录B
  • 有两个文件A/foo.pyB/foo.py
  • 这两个文件A/foo.pyB/foo.py都包含函数bar,定义在第100到120行之间
  • 这两个文件A/foo.pyB/foo.py之间有一些小的差异

在这种情况下,使用交错的方法来正确识别调用的是哪个bar是不可行的(如果我错了请纠正我),因为这需要静态分析每个bar中的代码,而这对于复杂的函数来说是非常困难的。

那么,我该如何获得我需要的正确跟踪输出呢?

1 个回答

1

通过一些小的修改,这个其实很简单。深入研究一下trace模块的源代码,我们可以发现它使用回调函数来报告每一步的执行情况。Trace.run的基本功能,简单来说就是:

sys.settrace(globaltrace)   # Set the trace callback function
exec function               # Execute the function, invoking the callback as necessary
sys.settrace(None)          # Reset the trace

globaltrace是在Trace.__init__中定义的,具体取决于传入的参数。特别是,在你第一个例子中的参数下,Trace.globaltrace_lt被用作全局回调,它会在每一行执行时调用Trace.localtrace_trace。如果想要改变这个行为,只需要修改Trace.localtrace,这样就能得到你想要的结果:

import trace
import sys
import time
import linecache

class Trace(trace.Trace):
    def localtrace_trace(self, frame, why, arg):
        if why == "line":
            # record the file name and line number of every trace
            filename = frame.f_code.co_filename
            lineno = frame.f_lineno

            if self.start_time:
                print '%.2f' % (time.time() - self.start_time),
            print "%s (%d): %s" % (filename, lineno,
                                  linecache.getline(filename, lineno)),
        return self.localtrace


tracer = Trace(count=False, trace=True, ignoredirs=[sys.prefix, sys.exec_prefix])
r = tracer.run('run()')

你给出的两个例子之间是有区别的;在第一个例子中,输出是在Trace.run调用期间打印的,而在第二个例子中,输出是在write_results期间打印的。我上面给出的代码遵循的是前者的模式,所以tracer.results().write_results()并不是必须的。不过,如果你想要处理这个输出,可以通过类似的方式修改trace.CoverageResults.write_results方法来实现。

撰写回答