深入 interrogate 源码:AST 遍历如何统计 docstring 覆盖率
深入 interrogate 源码AST 遍历如何统计 docstring 覆盖率【免费下载链接】interrogateExplain yourself! Interrogate a codebase for docstring coverage.项目地址: https://gitcode.com/gh_mirrors/in/interrogateinterrogate 是一个用于统计 Python 项目 docstring 覆盖率文档覆盖率的开源检查工具它通过 Python 内置的AST抽象语法树遍历技术逐模块、逐类、逐函数地检查代码中是否存在 docstring并最终给出一个类似单元测试覆盖率的百分比。本文将以源码为线索带你完整走一遍 interrogate 从「读取文件 → AST 解析 → 节点遍历 → 规则过滤 → 统计汇总」的整个流程帮助你真正理解 docstring 覆盖率是如何算出来的也为你自己动手实现类似的静态检查工具提供参考。 想边读边看代码可先克隆仓库git clone https://gitcode.com/gh_mirrors/in/interrogate一、interrogate 统计 docstring 覆盖率的整体流程在深入每一段源码之前先建立整体认知。interrogate 的核心工作流可以概括为一条清晰的流水线收集文件根据传入路径递归寻找所有.py/.pyi文件并应用排除规则解析源码对每个文件调用ast.parse()生成 AST 语法树遍历节点用自定义的CoverageVisitor深度遍历 AST为每个「可文档化节点」模块、类、函数/方法生成一条覆盖记录规则过滤根据配置忽略私有方法、魔术方法、嵌套函数等剔除不该统计的节点统计汇总累加 total / covered / missing算出百分比并决定进程退出码。整个流程的骨架位于 src/interrogate/coverage.py 的InterrogateCoverage类中而真正的遍历逻辑则藏在 src/interrogate/visit.py。二、入口调用链CLI 如何一步步走到 AST 遍历先从命令行入口看起。src/interrogate/cli.py 使用click定义了多达 30 个可配置选项--ignore-magic、--ignore-private、--fail-under、--style等main()函数会把这些参数组装成一个InterrogateConfig配置对象再交给InterrogateCoverageconf int_config.InterrogateConfig(...) interrogate_coverage coverage.InterrogateCoverage(pathspaths, confconf, ...) results interrogate_coverage.get_coverage()get_coverage()先通过get_filenames_from_paths()扫描出所有待检查的文件默认还会自动排除.git、.venv、.tox等目录见COMMON_EXCLUDE随后对每个文件执行_get_file_coverage()——AST 遍历的主战场就在这里。三、核心机制一ast.parse 与 CoverageVisitor 深度遍历3.1 把源码变成语法树在 coverage.py 的_get_file_coverage()中一段极简代码完成了源码到语法树的转换source_tree f.read() parsed_tree ast.parse(source_tree) visitor visit.CoverageVisitor(filenamefilename, configself.config) visitor.visit(parsed_tree)ast.parse是 Python 标准库能力它会把 Python 源码编译成 AST抽象语法树。AST 中每个代码结构都是一个节点模块对应ast.Module类对应ast.ClassDef普通函数对应ast.FunctionDef异步函数对应ast.AsyncFunctionDef。3.2 CoverageVisitor 如何遍历src/interrogate/visit.py 定义了CoverageVisitor它继承自ast.NodeVisitor。NodeVisitor是 Python 内置的「访问者模式」实现你只要定义visit_Module、visit_ClassDef、visit_FunctionDef等方法visit()就会自动把对应类型的节点分派给它们。interrogate 的实现非常巧妙只重写了四个方法其余一律交给_visit_helper()处理visit_Module处理模块级 docstringvisit_ClassDef处理类先做忽略判断visit_FunctionDef/visit_AsyncFunctionDef处理普通函数、方法、异步函数先做忽略判断。_visit_helper()的核心逻辑包含三件事生成 CovNode 记录每个可文档化节点都会被包装成一个CovNode记录名称、伪导入路径如sample.py:Foo.method_foo、层级、行号、是否有 docstring、是否嵌套等元数据维护一个栈self.stack进入节点时压栈、遍历完子节点后弹栈栈顶即当前节点的「父节点」从而能准确还原类与方法的父子关系、计算缩进层级level递归下降通过self.generic_visit(node)继续深入子节点保证「类里的方法、函数里的嵌套函数」都不会漏掉。这就解释了为什么 interrogate 的输出中能出现Bar.method_bar.InnerBar这样的三级嵌套路径——它靠的就是栈式遍历。四、核心机制二一个节点到底算不算「有 docstring」判断节点是否被文档覆盖逻辑在 visit.py 的_has_doc()中同样只有几行staticmethod def _has_doc(node): return ( ast.get_docstring(node) is not None and ast.get_docstring(node).strip() ! )这里用到的是ast.get_docstring()—— 它能自动识别函数、类、模块的首个语句是否为字符串字面量并且会自动去除缩进这正是它比手动取body[0]更可靠的原因。注意第二个条件空白 docstring只有空格换行不算覆盖这个细节很值得学习。另外interrogate 还支持--style google当类或其__init__方法任一有 docstring 时两者都视为已覆盖见 coverage.py 的_set_google_style()更贴近 Google 风格的文档习惯。五、忽略规则哪些节点被「排除」在统计之外统计 docstring 覆盖率时不是所有代码节点都参与计算。interrogate 提供了大量精细化规则全部集中在CoverageVisitor的_is_func_ignored()/_is_class_ignored()/_is_ignored_common()中visit.py。常见的忽略场景包括配置项作用--ignore-private忽略__xxx双下划线开头的私有类/方法/函数--ignore-semiprivate忽略_xxx单下划线开头的半私有成员--ignore-magic忽略__str__这类魔术方法不含__init__--ignore-init-method忽略__init__方法--ignore-init-module忽略__init__.py模块--ignore-nested-functions/--ignore-nested-classes忽略嵌套函数/嵌套类--ignore-property-decorators/--ignore-setters忽略property/ setter 方法--ignore-overloaded-functions忽略typing.overload装饰的函数--ignore-regex/--whitelist-regex按正则精确控制黑白名单例如_is_private()的判断非常严谨名字以__结尾即魔术方法不视为私有只有「以__开头且不以__结尾」才算私有避免了__init__被误伤。这些规则在遍历之前生效visit_ClassDef里先判忽略再_visit_helper而文件级过滤如ignore_module、include_regex则在遍历之后由 coverage.py 的_filter_nodes()完成形成「遍历时过滤 遍历后过滤」的双层机制。六、覆盖率数字是怎么算出来的统计与汇总遍历 过滤结束后就到了「出数字」的环节。核心是InterrogateFileResult.combine()coverage.pyfor node in self.nodes: if node.node_type Module and self.ignore_module: continue self.total 1 if node.covered: self.covered 1 self.missing self.total - self.coveredtotal参与统计的节点总数covered有 docstring 的节点数missing缺失 docstring 的节点数。最终百分比由perc_covered属性计算covered / total * 100。有个贴心的细节当 total 为 0 时返回 100%避免空项目被误判为 0 分见 coverage.py。InterrogateResults再对所有文件做一次combine()汇总出全局结果并与--fail-under阈值比较低于阈值则ret_code 1让 CI 构建失败——这就是「用覆盖率卡文档」的机制。七、把数字变成报告和徽章统计完成后interrogate 提供三种可读性极佳的产出摘要模式-v每个文件一行列出 Total / Miss / Cover / Cover%末尾有 TOTAL 行输出效果见 tests/functional/fixtures/expected_summary.txt详细模式-vv按行号排序逐个列出模块、类、方法及其 COVERED/MISSED 状态并带缩进体现层级参考 expected_detailed.txt徽章-g直接生成 shields.io 风格的 SVG 徽章随覆盖率变化自动换色≥95% 亮绿、≥90% 绿、≥60% 黄……实现代码在 src/interrogate/badge_gen.py。有了这些产出你可以轻松把 docstring 覆盖率挂到 README 或 CI 看板上。八、总结你能从 interrogate 源码中学到什么回顾整个实现interrogate 的架构其实非常「教科书」标准库优先AST 解析和遍历完全依赖ast标准库零第三方解析依赖代码量小、可读性高访问者模式 栈NodeVisitor天然适配树形遍历配合显式维护的栈来还原父子关系思路清晰双层过滤遍历时按「节点性质」过滤、遍历后按「文件级配置」过滤职责分离精确的边界判断私有/魔术/半私有的区分、空 docstring 的处理、total0 的兜底处处体现严谨。如果你正在写自己的代码质量检查工具或想加深对 AST 的理解src/interrogate/visit.py 和 src/interrogate/coverage.py 是两份不可多得的参考范例。而对普通开发者而言记住一句话就够了docstring 覆盖率 有 docstring 的代码节点数 ÷ 参与统计的代码节点总数interrogate 只是用 AST 遍历帮你把这件事自动化了。【免费下载链接】interrogateExplain yourself! Interrogate a codebase for docstring coverage.项目地址: https://gitcode.com/gh_mirrors/in/interrogate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考