尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

CPython trace 模块实战指南:命令行与编程接口追踪 Python 语句执行

CPython trace 模块实战指南:命令行与编程接口追踪 Python 语句执行 CPython trace 模块实战指南命令行与编程接口追踪 Python 语句执行【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpythonPython 标准库的trace模块提供了语句级执行追踪能力可以逐行打印执行过程、统计每行语句的执行次数生成带注释的覆盖率清单、列出运行期间被调用的函数、以及记录调用者/被调用者关系。本文基于 CPython 仓库中的官方文档 Doc/library/trace.rst 与实现源码 Lib/trace.py完整讲解其命令行用法、全部参数、编程接口与底层实现机制帮助你在没有引入第三方工具的情况下完成覆盖率统计与调用链分析。模块能力概览trace模块源码位于 Lib/trace.py支持四类追踪模式可通过命令行python -m trace或以编程方式调用行计数count程序结束后为每个模块生成.cover注释文件标注每行语句执行了多少次行追踪trace在每行语句执行前打印该行函数列表listfuncs列出运行期间至少被调用一次的所有函数调用关系trackcalls记录“谁调用了谁”的调用者/被调用者对。文档同时指出若需要 HTML 输出和分支覆盖率等更高级功能可以考虑社区广泛使用的 Coverage.py 工具trace模块的优势则是零依赖、随解释器发行。从源码结构看trace的对外接口只有两个名字见 Lib/trace.py 顶部的__all__ [Trace, CoverageResults]其余如_Ignore、_fullmodname、_find_executable_linenos都是内部辅助函数不建议在应用代码中直接依赖。命令行用法基本调用最简单的用法是直接执行一个脚本并生成覆盖率文件python -m trace --count -C . somefile.py ...上述命令会执行somefile.py并在当前目录中为运行期间导入的所有Python 模块生成带执行计数的注释清单。注意somefile.py之后的参数会被原样传递main()中通过argparse.REMAINDER收集arguments并写入sys.argv见 Lib/trace.py。另有--module选项3.8 起加入可以追踪以模块方式运行的程序例如python -m trace -l --module timeit -n 1回归测试 Lib/test/test_trace.py 的test_run_as_module验证了该用法且确认了追踪不存在的模块时会以失败退出。主选项Main options调用trace时必须至少指定以下主选项之一--report单独可用。其中--listfuncs与--trace、--count互斥给出--listfuncs时不接受--count/--trace反之亦然。这一约束在源码main()中实现为显式的parser.error(cannot specify both --listfuncs and (--trace or --count))检查见 Lib/trace.py回归测试 TestCommandLine.test_failures 逐条验证了所有错误信息。选项说明-c, --count程序结束后生成一组注释清单文件显示每条语句执行了多少次。配合--coverdir、--file、--no-report使用-t, --trace在每行执行前将该行打印出来-l, --listfuncs运行程序并显示期间被执行的函数与-c/-t互斥-r, --report从此前一次--count --file运行保存的计数文件生成注释清单不执行任何代码此时必须提供--file-T, --trackcalls显示运行程序暴露出的调用关系caller/callee--module以“可执行模块”而非脚本文件的方式运行目标3.8 新增修饰选项Modifiers选项说明-f, --filefile在多次追踪运行间累计计数的文件名应与--count一起使用-C, --coverdirdir报告文件输出目录。package.module的覆盖率报告写入{dir}/{package}/{module}.cover-m, --missing生成注释清单时用标记未被执行的行-s, --summary使用--count或--report时向标准输出为每个处理的文件写一段简短摘要只能配合--count/--report-R, --no-report不生成注释清单。适合计划做多次--count运行、最后统一生成一份清单的场景-g, --timing为每行输出“自程序启动以来的时间”仅在追踪tracing时生效其中--report与--no-report在 argparse 中被放入同一互斥组见 Lib/trace.py所以-r和-R不能同时给出。--no-report配合--file的“多次累计”工作流值得注意每次运行都会把计数合并进同一个 pickle 文件。测试 TestCommandLine.test_count_no_report_accumulates_counts 验证了这一点——同一脚本运行两次后计数文件中每行的值恰为单次运行值的两倍。过滤选项Filters过滤选项可重复出现多次选项说明--ignore-modulemod忽略给定模块名若为包则连同其子模块参数可以是逗号分隔的名称列表--ignore-dirdir忽略指定目录及其子目录下的所有模块与包参数可以是os.pathsep分隔的目录列表从源码实现看--ignore-dir在解析前会做用户目录/环境变量展开并支持特殊占位符$prefix标准库路径与$exec_prefix平台标准库路径由sysconfig.get_path解析见 Lib/trace.py。而模块级忽略由_Ignore.names实现除了精确匹配模块名还会用modulename.startswith(mod .)判断是否为忽略模块的子模块从而保证忽略Spam时Spam.Eggs也会被忽略但忽略cmp不会影响cmpcache见 Lib/trace.py。目录忽略则通过filename.startswith(d os.sep)保证只有真正的子目录内容被忽略——避免/usr/local误伤/usr/local.py这类同名文件。输出示例以下示例可直接复现来自回归测试 TestCoverageCommandLineOutput 中验证的精确输出。对一个脚本# coding: iso-8859-15 x spœm if []: print(unreachable)执行python -m trace --count tmp.py后会在脚本同目录生成tmp.cover# coding: iso-8859-15 1: x spœm 1: if []: print(unreachable)再追加--missing即-m时未执行的行会被标记# coding: iso-8859-15 1: x spœm 1: if []: print(unreachable)格式规则来自CoverageResults.write_results_file见 Lib/trace.py命中行的行前缀是%5d:右对齐 5 位的执行次数未覆盖的可执行行写其余行写 7 个空格源文件的制表符会被展开为 8 列宽且输出文件使用与源文件一致的编码通过tokenize.detect_encoding探测。--summary的摘要行格式为lines cov% module (path)例如 test_count_and_summary 中断言了6 100.0% {modulename} ({filename})这样的输出具体拼装逻辑在write_results末尾见 Lib/trace.py。编程接口Trace 类class Trace(count1, trace1, countfuncs0, countcallers0, ignoremods(), ignoredirs(), infileNone, outfileNone, timingFalse)创建一个用于追踪单条语句或表达式执行的对象所有参数均可选count启用行号计数trace启用行执行追踪countfuncs启用“运行期间被调用的函数”列表countcallers启用调用关系追踪ignoremods要忽略的模块或包名列表ignoredirs要忽略的目录列表其下所有模块/包均被忽略infile读取已存计数信息的文件名outfile写出更新后计数信息的文件名timing显示相对于追踪开始时间的时间戳。从源码看构造函数根据参数组合把self.globaltrace绑定到不同的全局跟踪回调见 Lib/trace.pycountcallers优先于countfuncs二者又优先于trace/count的任意组合只有当所有开关都为假时对象才进入donothing空转状态。这一优先级解释了为何命令行强制--listfuncs与--count/--trace互斥——底层实现上两者走的是互斥的回调分支。run(cmd)执行命令并按当前追踪参数收集统计。cmd必须是字符串或 code 对象可直接传给exec。run的实现是取__main__.__dict__作为全局/局部命名空间后转调runctx见 Lib/trace.py。runctx(cmd, globalsNone, localsNone)与run相同但使用指定的全局/局部环境执行未指定时默认空字典。内部通过sys.settrace(self.globaltrace)与threading.settrace(...)安装追踪钩子exec结束后在finally中复位见 Lib/trace.py——threading.settrace让此后新创建的线程也带上同一个全局跟踪函数这也是它能捕获到被追踪程序中跨线程代码的原因。runfunc(func, /, *args, **kwds)在Trace对象的当前追踪参数控制下用给定参数调用func并返回其结果。该方法的func是位置参数positional-onlytest_arg_errors 验证了runfunc(func...)会抛TypeError。results()返回一个CoverageResults对象包含该Trace实例此前所有run、runctx、runfunc调用的累计结果且不会重置已累计的追踪数据见 Lib/trace.py。CoverageResults 类覆盖率结果容器由Trace.results()创建用户不应直接构造。update(other)将另一个CoverageResults对象的数据合并进来。合并语义见 Lib/trace.py计数按键相加counts[key] counts.get(key, 0) other_counts[key]函数与调用者集合按键取并集。write_results(show_missingTrue, summaryFalse, coverdirNone, *, ignore_missing_filesFalse)写出覆盖率结果show_missing是否显示没有命中的行即生成可执行行表并标记summary是否在输出中包含每个模块的覆盖率摘要coverdir覆盖率结果文件的输出目录为None时每个源文件的.cover结果写到其自身所在目录ignore_missing_files3.13 新增关键字参数为True时静默忽略源文件已不存在的计数否则缺失文件会抛出FileNotFoundError。从源码看write_results的完整流程是见 Lib/trace.py先打印已调用函数列表与调用关系若存在再把(filename, lineno) → count的扁平计数重组为按文件分组的字典逐文件写出.cover文件最后若有summary则按lines cov% module (path)格式打印排序后的摘要并调用_save_counts把累计计数以 pickle 协议 1 写入outfile。一个值得留意的实现细节未执行的行并非“所有没有计数的行”。show_missingTrue时会调用_find_executable_linenos(filename)见 Lib/trace.py它把源文件编译成 code 对象用dis.findlinestarts收集字节码行号起点并递归进入co_consts中的嵌套 code 对象函数、生成器、推导式等再减去由_find_strings识别出的 docstring 行——因此只标记可执行的未覆盖行。另外包含#pragma NO COVER的行会被豁免标记常量PRAGMA_NOCOVER #pragma NO COVER见 Lib/trace.py 与 L323。编程接口示例文档给出的最小示例亦见 Lib/trace.py 模块 docstring 的“Sample use, programmatically”import sys import trace # 创建 Trace 对象指定忽略项选择“计数”而非“逐行追踪” tracer trace.Trace( ignoredirs[sys.prefix, sys.exec_prefix], trace0, count1) # 用该 tracer 运行命令 tracer.run(main()) # 在当前目录生成报告 r tracer.results() r.write_results(show_missingTrue, coverdir.)要点说明ignoredirs[sys.prefix, sys.exec_prefix]是排除标准库的惯用写法否则报告会被解释器自身模块淹没。回归测试test_coverage_ignore见 Lib/test/test_trace.py展示了更强的排除策略——把sys.path上的目录全部加入忽略列表后最终只剩_importlib.cover一个文件coverdir.时package.module的报告写入./package/module.covercoverdirNone时.cover文件直接落在各源文件旁边tracer.run(main())在__main__的命名空间中执行字符串命令因此main()必须已在__main__中定义。底层实现机制基于 sys.settrace 的两级回调trace完全建立在 Python 的语句级追踪协议sys.settrace/threading.settrace之上采用“全局回调 局部回调”两级结构全局回调在每次call事件时决定是否进入追踪globaltrace_lt读取帧的__file__经_modname得到模块名交给_Ignore.names判定忽略与否不忽略且开启trace时打印--- modulename: ..., funcname: ...分隔行并返回对应的localtrace函数接管后续事件见 Lib/trace.py局部回调处理line事件localtrace_trace_and_count同时打印行并累计self.counts[(filename, lineno)]localtrace_count只累计localtrace_trace只打印见 Lib/trace.py。计数键统一是(filename, lineno)元组localtrace_trace_and_count中还可以看到timing的实现——start_time在Trace构造时若timingTrue用time.monotonic记录之后每行前缀%.2f % (_time() - self.start_time)。函数与调用者追踪的实现countfuncsglobaltrace_countfuncs在call事件时把(filename, modulename, funcname)记入_calledfuncs见 Lib/trace.pywrite_results将其以filename: ..., modulename: ..., funcname: ...的排序列表打印——对应-l/--listfuncs的“functions called:”输出countcallersglobaltrace_trackcallers记录(_callers[(parent_func, this_func)] 1父帧取自frame.f_back见 Lib/trace.pywrite_results按被调用文件分组打印module.func - module.func关系方法归属file_module_function_of借助gc.get_referrers从 code 对象反查所属类从而把方法名写成ClassName.method它要求引用链唯一每层恰好一个引用者源码注释引用了“面对歧义拒绝猜测”的设计原则见 Lib/trace.py。TestCallers.test_loop_caller_importing见 Lib/test/test_trace.py给出的期望数据结构很说明问题调用者集合包含(trace, Trace.runfunc) - (traced_func_importing_caller,)这样的对即runfunc本身作为顶层调用者也被记录在内。计数文件的序列化与合并--file指定的计数文件使用pickle协议 1保存三元组(counts, calledfuncs, callers)写出_save_counts见 Lib/trace.py写出失败时仅向 stderr 提示而不中断读入合并CoverageResults.__init__打开infile时用self.update(...)合并进已有计数读取失败文件损坏、不存在时打印Skipping counts file ...后继续见 Lib/trace.py。测试test_coverageresults_update见 Lib/test/test_trace.py验证了空结果对象可以从非空 infile 合并得到数据。行计数的精确语义来自回归测试的观察Lib/test/test_trace.py 用白盒方式精确断言了Trace的计数语义可以据此建立对输出数值的正确预期循环对traced_func_loop循环体内一行c y、循环 5 次runfunc后期望计数为for行 1 次、循环条件/迭代行 6 次、循环体 5 次test_traced_func_loop。这与 CPython 字节码的行号表行起始点对应生成器生成器函数的行在“被调用”与“被next()驱动”两个阶段分别计数test_trace_func_generator断言了调用方循环行 11 次1 次调用 10 次迭代等具体数值test_trace_func_generator列表推导式推导式是独立的嵌套 code 对象其目标函数的行数按实际迭代次数计入test_trace_list_comprehension装饰器test_traced_decorated_function展示了装饰器各层函数各计 1 次而定义装饰器的外层行计 2 次定义调用各一次test_traced_decorated_function。这些用例中多处带有unittest.skipIf(os.environ.get(PYTHON_UOPS_OPTIMIZE) 0, ...)标记如 test_traced_func_loop提示在当前开发版解释器中禁用 JIT 优化器会改变行计数复现测试数据时应注意该环境前提。实践建议覆盖率基线python -m trace --count --summary --ignore-moduleunittest script可在零依赖下得到每个模块的lines / cov%摘要需要标记未覆盖行时加--missing。多次运行合并长任务或需要合并多个测试用例的覆盖时多次执行--count --no-report --file counts.pkl最后python -m trace --report --file counts.pkl --coverdir out/一次性出报告--report不执行任何代码。噪音过滤默认情况下标准库也会被追踪文档示例python -m trace --count -C . somefile.py即会输出“所有导入模块”的清单实用场景中建议用--ignore-dir排除解释器目录或用--ignore-module排除无关模块。性能敏感场景trace基于逐语句回调开销显著适合中等规模代码的调试与基线统计大规模、高频运行的覆盖率收集更适合专门的覆盖率工具文档的seealso即指向 Coverage.py。相关文档与源码索引文档Doc/library/trace.rst实现Lib/trace.py测试Lib/test/test_trace.py、追踪目标模块 Lib/test/tracedmodules/testmod.py相近主题语句追踪协议另有sys.settrace见 Doc/library/sys.rst测试 Lib/test/test_sys_settrace.py 覆盖更多代码块的追踪行为test_trace.py头部注释即提示参考。【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表