对于一个优秀的开源项目,高质量的文档与代码同样重要,糟糕的文档会将大部分潜在用户拒之门外。
从流行的开源项目中,我们可以总结出一些关于文档的最佳实践:
- 文档和代码库在一个仓库中维护以做到同步更新(个别体量超大的项目例外)
- 使用纯文本格式编写文档,如
markdown
或reStructText
,然后通过生成工具将其转换成易于阅读且样式丰富的静态网页,进一步可通过 CI 将静态网页托管到可公开访问的站点 - 在文档中直接引用实际的代码,并提供 API 文档
API 文档通常位于文档中的 API Reference
或 References
章节,它能节省用户查阅源码的时间,帮助用户快速理解项目的内部组成。对于作为库(而非应用)的项目而言,提供组织良好的 API 文档尤为重要,如 Flask、Click 等,本文就将介绍如何使用 Sphinx 为项目自动生成 API 文档。
示例项目所使用的的完整源码已推送到 GitHub 的 demo 仓库中。
为什么是 Sphinx
有许多开源工具可以用来将纯文本转换为静态网页,选择 Sphinx 的理由有以下几点:
- 经典老牌,使用相当广泛,Python 的官方文档也是用它所生成
- 功能全面,支持各种主题和插件,和 Python 具有语言上的亲和性
- 能够很方便地根据代码的文档字符串自动生成 API 文档
Sphinx 的默认文本格式是 reStructuredText(简称 rst),它是一种极其强大的标记语言,配合 Sphinx 可以实现很多 markdown 所不具备的解析、引用特性,缺点是规则和指令较为复杂。这原本并不算缺点,但随着 markdown 的流行,人们似乎无法再接受任何比 markdown 更复杂一点的标记语言了。
不过这并不是问题,我们也可以借助 MyST
解析器实现 Sphinx 和 markdown 的完全兼容,接下来的示例中我们将混合使用 md
和 rst
两种格式,当然你也可以只使用任意一种格式。
新建文档项目
安装 Sphinx
示例将是一个 Python 语言项目,因此最直接的方式当然是通过 pip 安装:
|
|
对于其他语言及平台, Sphinx 也提供了丰富的安装途径,详见 Installing Sphinx。
设置文档源目录
我们将在同一个项目中维护代码和文档,因此首先需要在项目根目录新建一个 docs
文件夹(也可以使用其他名称),用来存放所有和文档有关的文件,我们将使用该文件夹作为 Sphinx 工作的源目录(source directory)。
初始化项目
切换到 docs
目录,执行以下命令在该目录初始化一个新的 Sphinx 文档项目:
|
|
接下来根据命令行提示完成初始化项目的各项配置,如填写项目名称和作者,对于不理解的选项回车使用默认选项即可。
初始化后的 docs
目录内容如下:
|
|
其中最重要的是 conf.py
文件,它是一个 Sphinx 将会运行的 Python 模块,保存着刚刚通过命令行输入的配置,还可以编辑文件来修改配置或添加插件,并通过该文件运行需要的 Python 代码。
Makefile
和 make.bat
提供了一些快捷命令,在 docs
目录执行 make html
即可通过源文件生成静态网页。
index.rst
是文档源文件的首页,文档里有一些默认的样板内容,通常我们将其作为访问其他页面的入口目录。
此时已经可以运行 make html
生成静态网页用于预览,生成的 HTML 页面保存在 _build/html
目录。使用浏览器打开后效果如下:
如果对默认的样式不满意,还可以在 Sphinx Themes Gallery 找到大量可以轻松替换的文档主题。
基本使用
初识 reStructuredText
接下来我们借助 index.rst
简单认识一下 reStructuredText 格式。
标题
1 2
Welcome to sphinx-demo's documentation! =======================================
在文本的下一行连续输入同一标点符号,即可将文本标记为标题,另外还有很违反直觉的两点要求:
- 符号的长度需要超出文本
- 无法显式地指定标题级别,如果解析到另一种符号标记的标题,将根据出现顺序指定为新的标题级别,如
1 2 3 4 5
Secondary Title ---------------- Third Title ~~~~~~~~~~~~
相比 markdown 直接使用符号的个数指示标题级别,这种规则并不算直观。
目录树
1 2 3 4 5 6
.. toctree:: :maxdepth: 2 :caption: Contents: install reference
形如
.. {directive}::
这样的文本在rst
中被称为指令,可以实现各种特殊效果,比如.. toctree::
将在当前位置插入一颗目录树。在指令的下一行可以通过:{option}: value
指定选项,如上示例中指定了该目录树的最大深度和标题。再往下就是目录树的条目了,Sphinx 将根据条目名称如
install
作为相对路径在源目录即docs
中寻找名为install.rst
或install.md
的文件,并生成指向该文件的链接。引用
1 2 3 4 5 6
Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search`
这部分内容包含了 3 种格式:
首先是我们上面介绍过的标题。
然后是和 markdown 类似地通过
*
渲染列表项。最后是
:ref:`label_name`
,生成一条指向label_name
标签的交叉引用,这时需要通过.. _label_name:
指令提前创建一个全局唯一的标签,如为一个标题创建标签:1 2 3 4
.. _label_name: Section to cross-reference --------------------------
我们仅简单介绍几种在 index.rst
中出现过的格式,实际上 reStructuredText 能实现的功能远不限于此。
添加 markdown 支持
为 Sphinx 添加 markdown 支持非常简单。
首先安装 MyST
插件:
|
|
接着修改 conf.py
文件,找到 extensions = []
所在的一行,向该列表中添加 "myst_parser"
:
|
|
之后源目录中所有的 .md
文件就会像 .rst
一样被 Sphinx 正常解析。
添加新的文档内容
向首页添加新的文本段落
在已有的标题和指令之外直接插入文本即可,如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Welcome to sphinx-demo's documentation! ======================================= .. toctree:: :maxdepth: 2 :caption: Contents: Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` 新内容 ====== 这是一个新的段落
生成 HTML 页面如下:
添加新的
.rst
文件在
docs
目录中创建一个新的install.rst
,在文件中写入标题,然后在首页文件中添加相应的目录条目或者引用,本例中我们添加到目录树:1 2 3 4 5
.. toctree:: :maxdepth: 2 :caption: Contents: install
添加新文件时需要注意两点:
- 新文件中需要至少有一个标题,否则不会生成相应的链接,且链接默认会使用该标题作为展示文本。
- 在引用中添加子目录前缀即可在源目录中通过子目录管理文件。
添加新的
.md
文件我们尝试在
docs
中创建一个子目录references
,然后在该目录中添加api_reference.md
文件,接着在index.rst
文件中添加以下内容引用新添加的文件:1 2 3 4
Appendix ========= :doc:`references/api_reference`
实质上与添加
.rst
文件完全相同,因此新的文档中也需要包含标题才能生成链接。
最终通过 make html
生成的 HTML 页面效果如下:
生成 API 文档
以上介绍的内容,已经足够为项目编写一般的文本内容文档,最后来看如何通过代码自动生成 API 文档。
以单文件模块作为示例,结果同样适用于多文件的包和模块,假设我们的代码位于根目录下的 main.py
文件(即 main
模块)中,内容如下:
|
|
启用扩展
Sphinx 通过 autodoc
扩展导入源代码并解析其文档字符串转换成文档。此外还可通过 viewcode
扩展直接从文档访问 API 对应的源代码页面。
首先编辑 conf.py
文件以启用扩展,在 extensions 配置项中加入两个扩展项:
|
|
为了获取文档字符串,Sphinx 需要能够导入文档所在的代码模块。也就是说,我们的源码模块必须位于 Sphinx 的导入层级结构中,这有许多种实现途径,本例选择在 conf.py
中将代码模块所在的目录(即 conf.py
文件所在的上一级目录)加入到 sys.path
中,在 conf.py
中加入以下代码:
|
|
需要注意被 Sphinx 导入时模块中的代码会被执行一次,因此我们应该隔离模块中的执行逻辑以避免产生副作用,如将其包裹在 if __name__ == '__main__'
代码块中。
导入文档
最后,在 references/api_reference.md
文档中通过 automodule
指令导入 main
模块的文档:
|
|
注意:
除了
automodule
,还可使用autoclass
、autofunction
等指令为其他类型的 Python 对象自动生成文档。为了在 markdown 格式中使用
autodoc
,需要使用eval-rst
指令对导入文档的指令做一层封装,详见 Use sphinx.ext.autodoc in Markdown files,它等价于.rst
文件的如下写法:.. automodule:: main :members:
references/api_reference.md
生成的页面效果如下:
通过页面右侧的 [source]
链接还能够直接从文档页面跳转到相应的源代码:
文档字符串风格
上面的示例代码中,我们使用 reStructuredText 风格编写文档字符串,如果你更喜欢 NumPy 或 Google 风格的文档字符串,可以启用 napoleon
扩展与之兼容。napoleon
是一个预处理器,在 autodoc
处理文档字符串之前将其转换为正确的 reStructuredText 文本。
启用方法同样是修改 conf.py
文件的 extensions 配置项:
|
|
启用后 autodoc
将能够同时支持 reStructuredText、NumPy 及 Google 三种不同风格的文档字符串。
其他途径
除了 Sphinx ,使用 MkDocs 配合 mkdocstrings
扩展包也能实现自动从源码生成文档。相比之下 MkDocs 配置更加简单,原生支持 markdown 且可以运行预览用的开发服务器,目前在 Python 项目中也非常流行。