在 Sphinx 中描述代码

教程的前几节中,你可以阅读如何在 Sphinx 中编写叙事或散文文档。在本节中,你将改为描述代码对象。

Sphinx 支持记录多种语言的代码对象,包括 Python、C、C++、JavaScript 和 reStructuredText。每种语言都可以使用一系列指令和角色进行记录,这些指令和角色按分组。在教程的剩余部分,你将使用 Python 域,但本节中介绍的所有概念也适用于其他域。

Python

记录 Python 对象

Sphinx 提供了多种角色和指令来记录 Python 对象,所有这些都包含在Python 域中。例如,你可以使用py:function 指令记录 Python 函数,如下所示

docs/source/usage.rst
Creating recipes
----------------

To retrieve a list of random ingredients,
you can use the ``lumache.get_random_ingredients()`` function:

.. py:function:: lumache.get_random_ingredients(kind=None)

   Return a list of random ingredients as strings.

   :param kind: Optional "kind" of ingredients.
   :type kind: list[str] or None
   :return: The ingredients list.
   :rtype: list[str]

渲染结果如下

HTML result of documenting a Python function in Sphinx

在 Sphinx 中记录 Python 函数的渲染结果

请注意以下几点

  • Sphinx 解析了.. py:function 指令的参数,并适当地突出显示了模块、函数名称和参数。

  • 指令内容包含函数的单行描述,以及一个信息字段列表,其中包含函数参数、其预期类型、返回值和返回类型。

注意

前缀py:指定了。你可以配置默认域,以便省略前缀,全局配置可以通过primary_domain 配置进行设置,或者使用default-domain 指令从调用点到文件末尾更改它。例如,如果将其设置为py(默认值),则可以直接编写.. function::

交叉引用 Python 对象

默认情况下,大多数这些指令生成的实体都可以使用相应的角色从文档的任何部分进行交叉引用。对于函数,你可以使用py:func,如下所示

docs/source/usage.rst
The ``kind`` parameter should be either ``"meat"``, ``"fish"``,
or ``"veggies"``. Otherwise, :py:func:`lumache.get_random_ingredients`
will raise an exception.

生成代码文档时,Sphinx 只需使用对象名称即可自动生成交叉引用,无需显式为此使用角色。例如,你可以使用py:exception 指令来描述函数引发的自定义异常

docs/source/usage.rst
.. py:exception:: lumache.InvalidKindError

   Raised if the kind is invalid.

然后,将此异常添加到函数的原始描述中

docs/source/usage.rst
.. py:function:: lumache.get_random_ingredients(kind=None)

   Return a list of random ingredients as strings.

   :param kind: Optional "kind" of ingredients.
   :type kind: list[str] or None
   :raise lumache.InvalidKindError: If the kind is invalid.
   :return: The ingredients list.
   :rtype: list[str]

最后,结果如下所示

HTML result of documenting a Python function in Sphinx with cross-references

带有交叉引用的 Sphinx 中 Python 函数的 HTML 渲染结果

很漂亮,不是吗?

在文档中包含 doctest

由于你现在正在描述 Python 库中的代码,因此尽可能使文档和代码保持同步将非常有用。在 Sphinx 中实现此目的的一种方法是在文档中包含代码片段,称为doctest,这些代码片段在构建文档时会执行。

为了演示 doctest 和本教程中介绍的其他 Sphinx 功能,Sphinx 需要能够导入代码。为此,请在conf.py的开头编写以下内容

docs/source/conf.py
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here.
import pathlib
import sys
sys.path.insert(0, pathlib.Path(__file__).parents[2].resolve().as_posix())

注意

更改sys.path 变量的另一种方法是创建pyproject.toml 文件并使代码可安装,使其行为类似于任何其他 Python 库。但是,sys.path 方法更简单。

然后,在将 doctest 添加到文档之前,请在conf.py中启用doctest 扩展

docs/source/conf.py
extensions = [
    'sphinx.ext.duration',
    'sphinx.ext.doctest',
]

接下来,编写一个 doctest 块,如下所示

docs/source/usage.rst
>>> import lumache
>>> lumache.get_random_ingredients()
['shells', 'gorgonzola', 'parsley']

Doctest 包含要运行的 Python 指令,前面带有>>>(标准 Python 解释器提示符),以及每条指令的预期输出。这样,Sphinx 就可以检查实际输出是否与预期输出匹配。

要观察 doctest 失败的情况(而不是上面的代码错误),让我们首先错误地编写返回值。因此,添加一个名为get_random_ingredients 的函数,如下所示

lumache.py
def get_random_ingredients(kind=None):
    return ["eggs", "bacon", "spam"]

你现在可以运行make doctest 来执行文档的 doctest。最初,这将显示错误,因为实际代码的行为与指定的不符

(.venv) $ make doctest
Running Sphinx v4.2.0
loading pickled environment... done
...
running tests...

Document: usage
---------------
**********************************************************************
File "usage.rst", line 44, in default
Failed example:
    lumache.get_random_ingredients()
Expected:
    ['shells', 'gorgonzola', 'parsley']
Got:
    ['eggs', 'bacon', 'spam']
**********************************************************************
...
make: *** [Makefile:20: doctest] Error 1

如你所见,doctest 报告了预期结果和实际结果,以便于检查。现在是时候修复函数了

lumache.py
def get_random_ingredients(kind=None):
    return ["shells", "gorgonzola", "parsley"]

最后,make doctest 报告成功!

但是,对于大型项目,这种手动方法可能会变得有点繁琐。在下一节中,你将了解如何自动化此过程

其他语言 (C、C++ 等)

记录和交叉引用对象

Sphinx 还支持记录和交叉引用用其他编程语言编写的对象。有四个额外的内置域:C、C++、JavaScript 和 reStructuredText。第三方扩展可以为更多语言定义域,例如

例如,要记录 C++ 类型定义,可以使用内置的cpp:type 指令,如下所示

.. cpp:type:: std::vector<int> CustomList

   A typedef-like declaration of a type.

这将产生以下结果

typedef std::vector<int> CustomList

类型的类似 typedef 的声明。

所有这些指令都会生成可以使用的相应角色进行交叉引用的引用。例如,要引用前面的类型定义,可以使用cpp:type 角色,如下所示

Cross reference to :cpp:type:`CustomList`.

这将生成一个指向前面定义的超链接:CustomList