在 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 sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))

注意

變更 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