- 文档
- 开发工具
【免费下载链接】sphinx
The Sphinx documentation generator
本篇文章以仓库中的测试夹具 diagram_w_parts.rst 为切入点,完整讲解 Sphinx 内置sphinx.ext.inheritance_diagram扩展中inheritance-diagram指令及其核心选项:parts:的用法、渲染原理与测试验证方法。读完本文,你将掌握如何在自己的 Sphinx 文档中插入继承关系图、控制节点显示名称的粒度,并通过源码与测试理解该扩展的工作机制。
从一个测试夹具看起:diagram_w_parts.rst
仓库中 diagram_w_parts.rst 是test-inheritance测试根目录下的一个 RST 夹具文件,全文仅有一个指令示例:
Diagram using the parts option ============================== .. inheritance-diagram:: dummy.test :parts: 1这段内容展示了两件事:
inheritance-diagram指令以模块名dummy.test作为参数,将该模块内的全部类纳入继承图;:parts: 1选项让图中节点只显示类名本身,省略前面的模块路径前缀。
与该文件同目录的其他夹具(如 basic_diagram.rst、diagram_w_2_top_classes.rst 等)分别覆盖了无选项、top-classes、嵌套类等场景,形成了一组针对同一扩展的对比测试矩阵。该目录下的 index.rst 使用:glob:通配将这些夹具统一收进测试项目的 toctree。
夹具依赖的dummy.test模块定义在 dummy/test.py,其继承结构为:
class A: ... class B(A): ... class C(A): ... class D(B, C): ... class E(B): ... class F(C): ...对应关系图:
A / \ B C / \ / \ E D Fconf.py 通过sys.path.insert(0, str(Path.cwd().resolve()))让dummy包可被导入,并通过extensions = ['sphinx.ext.inheritance_diagram']启用该扩展——这两行配置是任何使用继承图功能的项目都必需的。
inheritance-diagram指令基础
在 Sphinx 官方文档 doc/usage/extensions/inheritance.rst 中,inheritance-diagram指令(自 Sphinx 0.6 起提供)接受一个或多个参数,每个参数可以是模块名或类名:
- 传入模块名时,该模块内定义的所有类都会被纳入图;
- 传入类名时,该类及全部基类会被纳入;
- 类名可以不带模块前缀,此时按
py:module指令设定的“当前模块”解析。
对每个给定类,扩展会向上递归确定其基类链,最终生成一张有向图,并交给 Graphviz 扩展渲染。因此该扩展在setup()中通过app.setup_extension('sphinx.ext.graphviz')显式依赖 Graphviz,构建环境需安装 Graphviz 的dot命令,否则指令会回退为警告而不是渲染出图。
默认用法(完整限定名):
.. inheritance-diagram:: dummy.test只显示类名(即本文主题:parts: 1):
.. inheritance-diagram:: dummy.test :parts: 1:parts:选项:控制节点显示名称的粒度
:parts:是inheritance-diagram指令最重要的选项之一,取值必须是整数。它的语义在 doc/usage/extensions/inheritance.rst 中有明确说明,在源码 sphinx/ext/inheritance_diagram.py 的InheritanceGraph.class_name()中实现:
def class_name(self, cls, parts=0, aliases=None): module = cls.__module__ if module in {'__builtin__', 'builtins'}: fullname = cls.__name__ else: fullname = f'{module}.{cls.__qualname__}' if parts == 0: result = fullname else: name_parts = fullname.split('.') result = '.'.join(name_parts[-parts:]) if aliases is not None and result in aliases: return aliases[result] return result对应三种取值形态:
| 取值 | 行为 | 示例(sphinx.ext.inheritance_diagram.InheritanceGraph) |
|---|---|---|
0(默认) | 显示完整点分隔限定名 | sphinx.ext.inheritance_diagram.InheritanceGraph |
| 正数 N | 从右向左保留 N 个部分 | :parts: 2显示inheritance_diagram.InheritanceGraph |
| 负数 -N | 从左向右丢弃 N 个部分 | :parts: -1显示ext.inheritance_diagram.InheritanceGraph |
关键在于name_parts[-parts:]这个切片:当parts为正数时取尾部 N 段;当parts为负数时,例如-1等价于name_parts[1:],恰好丢掉了最左侧的sphinx前缀。负数取值是 2.0 版本新增的能力(见 doc/usage/extensions/inheritance.rst 中versionchanged:: 2.0的记录),特别适合统一去掉公共前缀,例如所有类名都以lib.开头时,用:parts: -1一次性去除。
值得强调的是:parts只影响节点的“显示名”,不影响类的完整限定名(fullname)。在图结构中,节点标签使用class_name(cls, parts)的结果,而链接与去重使用class_name(cls, 0)的完整名称。这意味着即使图上只显示A,点击它仍然能跳转到dummy.test.A对应的文档页。
从源码看渲染链路
inheritance-diagram指令由 sphinx/ext/inheritance_diagram.py 中的InheritanceDiagram(一个SphinxDirective子类)实现,其选项表定义如下:
option_spec: ClassVar[OptionSpec] = { 'parts': int, 'private-bases': directives.flag, 'caption': directives.unchanged, 'top-classes': directives.unchanged_required, 'include-subclasses': directives.flag, }处理流程大致为:
run()中把参数按空白切分为类/模块名列表,读取parts、top-classes等选项;- 构造
InheritanceGraph,其中_class_info()(inheritance_diagram.py)从每个类出发递归遍历cls.__bases__,自底向上收集祖先,直到遇到object、内置类型(PY_BUILTINS,默认隐藏)或以_开头的私有基类(默认隐藏,除非开启private-bases); - 为图中每个完整类名生成
:class:交叉引用节点,供 HTML 输出时解析为可点击的 URL; - 将
graph对象存入节点,交由各构建器的 visit 方法渲染。
HTML 构建器在html_visit_inheritance_diagram()(inheritance_diagram.py)中生成 PNG 图 + 可点击的 image map;LaTeX 输出 PDF(latex_visit_inheritance_diagram),Texinfo 输出 PNG(texinfo_visit_inheritance_diagram),而 text 与 man 构建器直接跳过该节点。
一个容易被忽略的细节是输出文件名的生成(inheritance_diagram.py):
def get_graph_hash(node): encoded = (node['content'] + str(node['parts'])).encode() return hashlib.md5(encoded, usedforsecurity=False).hexdigest()[-10:]图的哈希由“指令内容 + parts 取值”共同决定。也就是说,同一份类列表配不同parts值会生成不同的图片文件,可以安全地在一篇文档中同时放置完整名和短名称两张图而互不冲突。
默认的 Graphviz 属性同样定义在扩展源码中(inheritance_diagram.py):
- 图级:
rankdir=LR(从左到右布局)、size="8.0, 12.0"、bgcolor=transparent; - 节点:
shape=box、fontsize=10、白色填充; - 边:
arrowsize=0.5。
这些默认值均可通过配置项覆盖(见下文“配置项”一节)。
测试如何验证:parts:的行为
仓库对parts选项的验证位于 test_ext_inheritance_diagram.py。测试用@pytest.mark.sphinx('html', testroot='inheritance')在test-inheritance根目录上构建 HTML,并临时替换InheritanceDiagram.run()截获每张图的class_info,随后对比两种场景:
- 无
parts的basic_diagram:节点显示名为完整限定名,如('dummy.test.A', 'dummy.test.A', (), None); - 带
:parts: 1的diagram_w_parts:节点显示名变成短名称,但 fullname 仍是完整路径,如('A', 'dummy.test.A', (), None),且边上的基类名也一并缩短,如('D', 'dummy.test.D', ('B', 'C'), None)。
class_info的元组结构为(显示名, 完整限定名, 基类显示名列表, 工具提示),直接反映了parts对图数据的双重影响:既缩短了节点标签,也缩短了边上的基类引用,两者保持一致。测试还断言构建过程app.statuscode == 0且无 HTML 告警,说明该夹具在启用扩展的默认配置下可以干净地构建通过。
该测试通过@pytest.mark.usefixtures('if_graphviz_found')在检测到 Graphviz 时才运行,印证了本扩展对 Graphviz 工具的运行时依赖。
其他常用选项一览
除了parts,同一指令还支持以下选项(均已在源码option_spec与官方文档中确认):
private-bases(flag,1.1 起):默认私有基类(名字以_开头)会被排除在图中,开启后将其一并纳入;caption(1.5 起):为图添加标题,渲染为带<figcaption>的 figure;top-classes(1.7 起):以逗号分隔一个或多个类名,继承遍历在这些类处停止,用于裁剪过大的继承图。注意已知问题:若参数传入的是整个模块,祖先类仍会以独立节点出现(如dummy.test搭配top-classes: dummy.test.B, dummy.test.C时,类A仍会显示为独立节点);想要彻底隐藏祖先,应只列出具体类名,如.. inheritance-diagram:: dummy.test.D dummy.test.E dummy.test.F;include-subclasses(flag,8.2 起):把指定类(或模块内类)的全部子类也加入图中,与top-classes方向相反地扩展图的范围。
配置项:全局定制继承图外观
扩展在setup()中注册了四个配置项(inheritance_diagram.py),可在项目的conf.py中覆盖默认外观:
| 配置项 | 类型 | 默认值 | 作用 |
|---|---|---|---|
inheritance_graph_attrs | dict[str, str \| int \| float \| bool] | {} | Graphviz 图级属性 |
inheritance_node_attrs | 同上 | {} | 节点属性 |
inheritance_edge_attrs | 同上 | {} | 边属性 |
inheritance_alias | dict[str, str] | {} | 类完整名到显示名的映射 |
例如官方文档 doc/usage/extensions/inheritance.rst 给出的定制示例:
inheritance_graph_attrs = dict(rankdir="LR", size='"6.0, 8.0"', fontsize=14, ratio='compress') inheritance_node_attrs = dict(shape='ellipse', fontsize=14, height=0.75, color='dodgerblue1', style='filled') inheritance_alias = {'_pytest.Magic': 'pytest.Magic'}从源码实现看,这些配置会在_generate_dot()(inheritance_diagram.py)中按“默认属性 → 构建器传入属性 → 用户配置”的优先级逐层覆盖,最终拼成 DOT 代码交给 Graphviz 渲染。inheritance_alias则作用于class_name()的收尾阶段:只要裁剪后的显示名命中别名键,就用别名替换,适合隐藏私有类的真实路径。
实践要点小结
- 启用扩展:在
conf.py中写入extensions = ['sphinx.ext.inheritance_diagram'],并确保构建机安装了 Graphviz; - 控制名称粒度:默认完整限定名;
:parts: 1只看类名;:parts: -N统一裁掉公共前缀; - 控制图的范围:用
top-classes裁剪祖先层级,用include-subclasses扩展子类;若需彻底隐藏祖先,应逐个列出目标类而非整个模块; - 定制外观:通过
inheritance_graph_attrs/inheritance_node_attrs/inheritance_edge_attrs覆盖默认 DOT 属性,inheritance_alias可隐藏私有类名; - 验证与调试:可直接复用仓库
tests/roots/test-inheritance下的夹具与 test_ext_inheritance_diagram.py 中的断言思路,检查class_info中的显示名与完整名是否符合预期。
- 文档
- 开发工具
【免费下载链接】sphinx
The Sphinx documentation generator
相关推荐
Sphinx 继承关系图扩展 inheritance_diagram 完全指南:用 Graphviz 自动生成类继承图
Sphinx 继承关系图扩展 inheritance_diagram 完全指南:用 Graphviz 自动生成类继承图 导读 本文围绕 Sphinx 内置扩展
文档开发工具Flower 退出码 607(COMMON_APP_IMPORT_ERROR)排查指南:Flower App 导入失败的定位与修复
Flower 退出码 607(COMMON_APP_IMPORT_ERROR)排查指南:Flower App 导入失败的定位与修复 导读 退出码 607( CO
文档开发工具OneUptime 连续性能剖析监控指南:Profile 监控器的配置、告警规则与 OpenTelemetry 集成
OneUptime 连续性能剖析监控指南:Profile 监控器的配置、告警规则与 OpenTelemetry 集成 本文围绕 OneUptime 的 Prof
文档开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考