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: