在 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 指令的参数,并适当地突出了模块、函数名和参数。

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

备注

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 中做到这一点的方法之一是在文档中包含代码片段,称为 doctests,在文档构建时被执行。

为了演示本教程中涉及的 doctests 和其他 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 的方法更简单。

然后,在向你的文档添加测试之前,在 conf.py 中启用 doctest 插件:

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

接下来,写一个测试块,如下所示:

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

Doctests 包括要运行的 Python 指令,前面有 >>,标准 的Python 解释器提示,以及每条指令的预期输出。这样,Sphinx 可以检查实际输出是否与预期的一致。

为了观察测试失败的样子(而不是像上面那样的代码错误),我们先把返回值写错。因此,添加一个函数 get_random_ingredients 像这样:

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

你现在可以运行 make 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 test 报告成功!

虽然对于大项目来说,这种手工方法可能会变得有点乏味。在下一节中,你将看到 如何使这个过程自动化

其他语言(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

A typedef-like declaration of a type.

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

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

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