使用 Sphinx 为 Python 生成文档的正确姿势

6 min read

使用一个有文档的程序 (库) 是一件幸福的事,但维护一套文档是一件痛苦的事。

为了将广大程序员从写文档的地狱中解救出来,程序员们发明了 (目前为止) 写文档的最佳实践 —— 文档代码化 —— 也就是将文档和代码写在一起,一般是通过注释的方式。文档代码化的好处这里就不再赘述了。

幸运的是 Python 是支持文档代码化的,通过 """ 注释。但作为 Python 文档之光的 Sphinx 做文档代码化的体验并不好。这也不能怪 Sphinx,毕竟 Sphinx 出生时,文档代码化的概念还没有被人们所熟知。

如果你在网上搜索“使用 Sphinx 为 Python 生成文档”,搜索到的方法多半是:

  1. 新建文件夹 doc(s)
  2. docs 中使用 sphinx-quickstart 新建一个 Sphinx 工程
  3. 配置 Sphinx 工程
  4. sphinx-apidoc 读取代码,生成 rst 文件

好的,现在你不仅要写代码,还要维护文档了,非常 old school!

我们可以看出,Sphinx 并没有逃出:写代码,写文档,写代码的地狱。正确的文档代码化实践应当是:

  1. 写代码,在注释中写文档
  2. 通过 CI 直接从代码中生成文档

好在最新版的 sphinx-apidoc 基本支持这一实践 (只是有点简陋)。

使用 sphinx-apidoc 自动生成文档

阅读最新的 sphinx-apidoc 文档 我们可以发现, sphinx-apidoc 实际上是可以生成一个完整的 Sphinx 工程的:

help_F

所以,其实我们并不需要 sphinx-quickstart,直接在 Python 工程中(使用 -f 强制生成):

sphinx-apidoc -f -F -o docs <code dir>

你还可以将它配置在 Makefile 中:

DOCS_CMD = sphinx-apidoc
PROJ_DIR = myq_spider
DOCS_DIR = docs

doc: $(DOCS_DIR)

$(DOCS_DIR): $(PROJ_NAME)
	@$(DOCS_CMD) -f -F -o $@ $<

html: $(DOCS_DIR)
	@$(MAKE) -C $< html

然后直接在 Python 工程中运行 make docsmake html 就可以生成、编译文档了。

然后配合 CI,就可以基本实现在不维护一套独立的 Sphinx 文档的前提下,自动生成 API 文档。

使用模板 (进阶)

目前为止,基于 sphinx-apidoc 的文档代码化已经基本能用了,但并不好用。

原因在于配置文件 conf.py 会在每次运行 make docs 时刷新到初始状态。项目名称、作者姓名等信息尚且可以通过 [OPTIONS] 传入:

help_aHAVR

但主题怎么办?那个丑不拉几的默认主题?看多了也不怕性冷淡…

还有各种插件也没法配置,只能用默认的…

好在 >=2.2 的版本增加了模板的支持。

help_t

我们首先将生成的 docs/conf.py 复制一份到 docs/_templates/conf.py_t

然后根据实际需要在 docs/_templates/conf.py_t 中修改配置。

最后在 Makefile 中指定存放模板的文件夹:

DOCS_CMD = sphinx-apidoc
PROJ_DIR = myq_spider
DOCS_DIR = docs

doc: $(DOCS_DIR)

$(DOCS_DIR):
	@$(DOCS_CMD) -f -F -t $@/_templates -o $@ $(PROJ_NAME)

html: $(DOCS_DIR)
	@$(MAKE) -C $< html

生成文档时,sphinx-apidoc 会以 docs/_templates/conf.py_t 作为模板生成 docs/conf.py,从而实现对 docs/conf.py 的配置。

当然你也可以复制并配置 Sphinx 安装目录中的 templates/quickstart/conf.py_t,以获得更好的动态灵活性。

.gitignore

既然选择了 CI 来自动生成 API 文档,那么 docs 文件夹中的 Sphinx 项目似乎就有点多余。

我们可以通过 .gitignore 来排除它们 (只包含模板和静态文件):

# Sphinx documentation
docs/*
!docs/_static
!docs/_templates

包含 README.rst

有时候我们会希望 Python 项目的 API 文档的首页包含本项目的 README.rst。

同样的,直接修改 docs/index.rst 是没用的,我们需要使用模板。

但这次不能直接复制 docs/index.rst 了,我们需要一点动态能力,复制并配置 Sphinx 安装目录中的 templates/quickstart/root_doc.rst_tdocs/_templates/root_doc.rst_t,然后修改 (找不到 Sphinx 安装目录的可以直接复制以下内容):

.. {{ project }} documentation master file, created by
   sphinx-quickstart on {{ now }}.
   You can adapt this file completely to your liking, but it should at least
   contain the root `toctree` directive.

Welcome to {{ project }}'s documentation!
==========={{ project_underline }}=================

.. toctree::
   :maxdepth: {{ mastertocmaxdepth }}
   :caption: Contents:

{{ mastertoctree }}

README.rst
==========

.. include:: ../README.rst

Indices and tables
==================

* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`