python-trace模块追踪代码执行流
1.概述
这篇文章介绍trace模块帮助查看程序的执行路径,生成覆盖报告帮助梳理函数间的调用关系
2.trace模块
2.1.测试数据
在介绍trace模块使用前,先创建示例需要的测试数据。
创建recurse.py文件
def recurse(level):
print('recurse({})'.format(level))
if level:
recurse(level - 1)
def not_called():
print('This function is never called.')
创建main.py文件
from recurse import recurse
def main():
print('This is the main program.')
recurse(2)
if __name__ == '__main__':
main()
2.2.命令行执行trace
1.trace模块命令和参数
trace 可直接从命令行中使用。下面是常用命令和参数
–help
显示使用情况并退出。
–version
显示模块版本并退出。
调用 trace 时,必须至少指定以下选项之一。 --listfuncs 选项与 --trace 和 --count 选项是互斥的。 当提供 --listfuncs 时,既不接受 --count 也不接受 --trace,反之亦然。
-c, --count
在程序完成时生成一组带注释的列表文件,显示每个语句的执行次数。 另请参阅下面的 --coverdir、–file 和 --no-report。
-t, --trace
在执行时显示行。
-l, --listfuncs
显示通过运行程序执行的功能。
-r, --report
从使用 --count 和 --file 选项的较早程序运行生成带注释的列表。 这不会执行任何代码。
-T, --trackcalls
显示通过运行程序暴露的调用关系。
-f, --file=
在多次跟踪运行中累积计数的文件名称。 应与 --count 选项一起使用。
-C, --coverdir=
报告文件所在的目录。 package.module 的覆盖率报告写入文件 dir/package/module.cover。
-m, --missing
生成带注释的列表时,标记未使用 >>>>>> 执行的行。
-s, --summary
使用 --count 或 --report 时,为每个处理的文件向标准输出写入简要摘要。
-R, --no-report
不要生成带注释的列表。 如果您打算使用 --count 进行多次运行,然后在最后生成一组带注释的列表,这将非常有用。
-g, --timing
用程序启动以来的时间作为每行的前缀。 仅在跟踪时使用。
–ignore-module=
忽略每个给定的模块名称及其子模块(如果它是一个包)。 参数可以是用逗号分隔的名称列表。
–ignore-dir=
忽略命名目录和子目录中的所有模块和包。 参数可以是由 os.pathsep 分隔的目录列表
2.追踪程序调用链
输出的第一部分是 trace 执行的设置步骤。其余部分展示的是进入每个函数的步骤,包括函数在模块中的位置,源代码中是哪一行执行了。我们可以看到 recurse() 进入了三次,正如我们调用 main() 所预期的一样。
python3 -m trace --ignore-dir=.../lib/python3.6\
--trace trace_example/main.py
--- modulename: main, funcname: <module>
main.py(7): """
main.py(10): from recurse import recurse
--- modulename: recurse, funcname: <module>
recurse.py(7): """
recurse.py(11): def recurse(level):
recurse.py(17): def not_called():
main.py(13): def main():
main.py(18): if __name__ == '__main__':
main.py(19): main()
--- modulename: main, funcname: main
main.py(14): print('This is the main program.')
This is the main program.
main.py(15): recurse(2)
--- modulename: recurse, funcname: recurse
recurse.py(12): print('recurse({})'.format(level))
recurse(2)
recurse.py(13): if level:
recurse.py(14): recurse(level - 1)
--- modulename: recurse, funcname: recurse
recurse.py(12): print('recurse({})'.format(level))
recurse(1)
recurse.py(13): if level:
recurse.py(14): recurse(level - 1)
--- modulename: recurse, funcname: recurse
recurse.py(12): print('recurse({})'.format(level))
recurse(0)
recurse.py(13): if level:
--- modulename: trace, funcname: _unsettrace
trace.py(77): sys.settrace(None)
3.查看函数调用关系
trace 模块还会收集并报告函数间的调用关系。使用 --listfuncs 可以列出简短的函数间调用关系:
python3 -m trace --listfuncs trace_example/main.py |\
grep -v importlib
This is the main program.
recurse(2)
recurse(1)
recurse(0)
functions called:
filename: .../lib/python3.6/trace.py, modulename: trace,
funcname: _unsettrace
filename: trace_example/main.py, modulename: main, funcname:
<module>
filename: trace_example/main.py, modulename: main, funcname:
main
filename: trace_example/recurse.py, modulename: recurse,
funcname: <module>
filename: trace_example/recurse.py, modulename: recurse,
funcname: recurse
要想知道详细的是谁调用了它,要用 --trackcalls
python3 -m trace --listfuncs --trackcalls\
trace_example/main.py | grep -v importlib
This is the main program.
recurse(2)
recurse(1)
recurse(0)
calling relationships:
*** .../lib/python3.6/trace.py ***
trace.Trace.runctx -> trace._unsettrace
--> trace_example/main.py
trace.Trace.runctx -> main.<module>
--> trace_example/recurse.py
*** trace_example/main.py ***
main.<module> -> main.main
--> trace_example/recurse.py
main.main -> recurse.recurse
*** trace_example/recurse.py ***
recurse.recurse -> recurse.recurse
注意
–listfuncs 和 --trackcalls 都不支持 --ignore-dirs 和 --ignore-mods 参数,所以我们用 grep 来代替它。
2.3.代码执行trace
为了控制更多 trace 的接口,我们可以在程序内使用 Trace 对象来调用。在运行单个函数或运行某个需要追踪的 Python 命令前,Trace 可以设置 fixture 和其他依赖。
import trace
from recurse import recurse
tracer = trace.Trace(count=False, trace=True)
tracer.run('recurse(2)')
我们只追踪了 recurse(),所以 main.py 的信息不包含在输出中。
python3 trace_run.py
--- modulename: trace_run, funcname: <module>
<string>(1): --- modulename: recurse, funcname: recurse
recurse.py(12): print('recurse({})'.format(level))
recurse(2)
recurse.py(13): if level:
recurse.py(14): recurse(level - 1)
--- modulename: recurse, funcname: recurse
recurse.py(12): print('recurse({})'.format(level))
recurse(1)
recurse.py(13): if level:
recurse.py(14): recurse(level - 1)
--- modulename: recurse, funcname: recurse
recurse.py(12): print('recurse({})'.format(level))
recurse(0)
recurse.py(13): if level:
--- modulename: trace, funcname: _unsettrace
trace.py(77): sys.settrace(None)
使用 runfunc() 可以达到同样的效果。
import trace
from trace_example.recurse import recurse
tracer = trace.Trace(count=False, trace=True)
tracer.runfunc(recurse, 2)
runfunc() 方法接受任意的位置和关键字参数,在 tracer 调用时会将他们传递给函数。
python3 trace_runfunc.py
--- modulename: recurse, funcname: recurse
recurse.py(12): print('recurse({})'.format(level))
recurse(2)
recurse.py(13): if level:
recurse.py(14): recurse(level - 1)
--- modulename: recurse, funcname: recurse
recurse.py(12): print('recurse({})'.format(level))
recurse(1)
recurse.py(13): if level:
recurse.py(14): recurse(level - 1)
--- modulename: recurse, funcname: recurse
recurse.py(12): print('recurse({})'.format(level))
recurse(0)
recurse.py(13): if level: