Skip to content

Latest commit

 

History

History
1036 lines (650 loc) · 61.2 KB

File metadata and controls

1036 lines (650 loc) · 61.2 KB

二十、质量和文档

要想有价值,软件必须是可信的。可信性的总体目标依赖于许多软件质量属性。有关这方面的更多信息,请参阅中的S-Cube 质量参考模型部分 https://s-cube-network.eu/km/qrm/index.html 。良好的文档是证明满足各种质量属性的基本技术。

在本章中,我们将看两个工具来根据代码生成文档:pydocSphinxpydoc工具从 Python 代码中提取文档,并生成有用的 docstring 视图。Sphinx工具允许我们使用轻量级标记语言结合源代码创建完整而复杂的文档。我们将描述重组文本RST的一些特性,以帮助我们的文档更具可读性。

更多信息,请参考中的 PEP 257https://www.python.org/dev/peps/pep-0257/ 。这描述了 docstring 的最低标准。使用诸如 RST 之类的格式化标记扩展了此基线方案。

完整的测试用例是高质量软件的另一个方面。我们可以使用doctest模块来执行测试用例。这样做可以解决两个质量方面的问题:文档编制和使用单个工具进行测试。

我们还将简要介绍识字编程技术。其想法是编写一个令人愉快、易于理解的文档,其中包含整个源代码主体以及解释性说明和设计细节。识字编程并不简单,但它可以生成良好的代码,并生成非常清晰完整的文档。

在本章中,我们将介绍以下主题:

  • help()函数写入 docstring
  • 使用 pydoc 进行文档编制
  • 通过 RST 标记实现更好的输出
  • 编写有效的文档字符串
  • 编写文件级 docstring,包括模块和包
  • 更复杂的标记技术
  • 使用Sphinx生成文档
  • 编写文档
  • 识字编程

技术要求

本章的代码文件可在上找到 https://git.io/fj2U9

为 help()函数编写 docstring

Python 提供了许多地方来包含文档。包、模块、类或函数的定义可以容纳包含所定义对象描述的字符串。

在本书中,我们避免在每个示例中向您展示冗长的 docstring,因为我们的重点是 Python 编程细节,而不是交付的整个软件产品。

当我们超越了高级 OO 设计并审视整个可交付产品时,docstring 成为可交付产品的重要组成部分。Docstring 可以为我们提供几个关键信息:

  • API:引发的参数、返回值和异常。
  • 对预期结果的描述。
  • (可选)doctest测试结果。更多信息请参见第 17 章可测试性设计

当然,我们可以在 docstring 中编写更多内容。我们可以提供更多关于设计、架构和需求的细节。在某种程度上,这些更抽象、更高层次的考虑并没有直接与 Python 代码联系在一起。这种更高层次的设计和需求不属于代码或文档串。

help()函数提取并显示文档字符串。它对文本执行一些最小的格式设置。help()函数由site包安装在交互式 Python 环境中。该函数实际上是在pydoc包中定义的。原则上,我们可以导入并扩展此包来定制help()输出。

编写适合help()的文档相对简单。以下是help(round)输出的典型示例:

Help on built-in function round in module builtins:
round(number, ndigits=None)
    Round a number to a given precision in decimal digits.

    The return value is an integer if ndigits is omitted or None. Otherwise
    the return value has the same type as the number. ndigits may be negative.

这向我们展示了所需的元素:摘要、API 和描述。API 和摘要是第一行:round(number, ndigits=None)

描述文本定义函数的功能。更复杂的函数可能描述异常或边缘情况,这些异常或边缘情况可能对该函数非常重要或独特。例如,round()函数没有详细说明可能引发的TypeError等元素。

面向help()的 docstring 应该是纯文本,没有标记。我们可以添加一些 RST 标记,但它不被help()使用。

为了使help()有效,我们只需提供 docstring。因为它很简单,没有理由不这样做。每个函数或类都需要一个 docstring,以便help()向我们展示一些有用的东西。

现在,让我们看看如何使用 pydoc 进行文档编制。

使用 pydoc 进行文档编制

我们使用库模块pydoc从 Python 代码生成 HTML 文档。事实证明,我们在交互式 Python 中评估help()时使用了它。此函数生成无标记的文本模式文档。

当我们使用pydoc生成文档时,我们将通过以下方式之一使用它:

  • 准备文本模式文档文件,并使用命令行工具(如moreless)查看它们。
  • 运行 HTTP 服务器并直接浏览文档。

我们可以运行以下命令行工具为模块准备基于文本的文档:

pydoc somemodule 

我们还可以使用以下代码:

python3 -m pydoc somemodule 

这两个命令都将基于 Python 代码创建文本文档。输出将通过诸如less(在 Linux 或 macOS 上)或more(在 Windows 上)之类的程序显示,这些程序对长的输出流进行分页。

通常,pydoc假定我们提供了一个要导入的模块名。这意味着,对于普通导入,模块必须位于 Python 路径上。或者,我们可以指定一个物理文件名。类似于pydoc ./mymodule.py的东西可以用来选择一个文件而不是一个模块。

该程序还可以启动一个专用的 web 服务器来浏览包或模块的文档。除了简单地启动服务器之外,我们还可以将启动服务器和启动默认浏览器结合起来。-b选项启动浏览器。这里有一种方法可以简单地启动服务器,同时启动浏览器:

python3 -m pydoc -b 

这将找到未使用的端口,启动服务器,然后启动默认浏览器指向服务器

定制pydoc的输出并不容易。各种样式和颜色有效地硬编码到类定义中。修改和扩展pydoc以使用外部 CSS 样式将是一个有趣的练习。

以下屏幕截图显示了默认样式:

现在,让我们看看如何使用 RST 标记获得更好的输出。

通过 RST 标记实现更好的输出

如果我们使用更复杂的工具集,我们的文档可能会更好。

我们希望能够做以下几件事:

  • 对演示文稿进行微调,使其包含粗体、斜体或颜色等强调。

  • 为 Python 对象之间的参数、返回值、异常和交叉引用提供语义标记。

  • 提供查看源代码的链接。

  • 筛选包含或拒绝的代码。这包括微调标准库模块、带前导_私有对象、带前导__的系统对象或超类成员的存在。

  • 调整 CSS 以为生成的 HTML 页面提供不同的样式。

我们可以通过 docstring 中更复杂的标记来满足前两个需求。我们需要使用 RST 标记语言,还需要一个额外的工具来满足最后三个需求。

一旦我们开始使用更复杂的标记,我们就可以扩展到 HTML 之外,包括,以获得更好看的文档。这允许我们除了从单个源生成 HTML 外,还可以生成 PostScript 或 PDF 输出。

RST 是一种简单、轻量级的标记。有很多与 Pythondocutils项目相关的好教程和总结。参见http://docutils.sourceforge.net 了解详情。

此处提供快速概述:http://docutils.sourceforge.net/docs/user/rst/quickstart.html

docutils工具集的要点是,非常智能的解析器允许我们使用非常简单的标记。HTML 和 XML 依赖于相对简单的解析器,并将创建复杂标记的负担放在了人(或编辑工具)身上。虽然 XML 和 HTML 允许各种各样的用例,但docutils解析器更狭隘地关注自然语言文本。由于焦点狭窄,docutils能够根据空行和一些 ASCII 标点符号的使用推断出我们的意图。

出于我们的目的,docutils解析器认识到以下三个基本方面:

  • 文本块:段落、标题、列表、块引号、代码示例和doctest块。这些都用空行隔开。
  • 内联标记可以出现在文本块内。这涉及到使用简单的标点符号来标记文本块中的字符。有两种内联标记;我们将在后面的部分中查看详细信息。
  • 指令也是文本块,但它们以..开头,作为行的前两个字符。指令是开放式的,可以扩展以向 docutils 添加功能。

书写可以转换成好看文档的文本的基本要素是清晰地分隔文本块。在下一节中,我们将研究不同类型的文本块。

文本块

文本块只是一个段落,由一个空行与其他段落隔开。这是 RST 标记的基本单位。RST 根据所遵循的模式识别多种类型的段落。以下是标题示例:

This Is A Heading 
================= 

这被认为是一个标题,因为它的下划线带有重复的特殊字符字符串。

docutils解析器完全根据标题下划线的用法推断其层次结构。我们必须与标题及其嵌套保持一致。选择一个标准并坚持它是有帮助的。它还有助于使文档保持相当平坦,没有复杂的嵌套标题。通常只需要三个层次;这意味着我们可以将====----~~~~用于三个级别。

项目符号列表项以特殊字符开头;内容也必须缩进。由于 Python 使用 4 空间缩进,这在 RST 中也很常见。但是,几乎任何一致的缩进都可以:

Bullet Lists 

-   Leading Special Character. 

-   Consistent Indent. 

注意段落之间的空行。对于一些简单的弹头列表,不需要空白行。一般来说,空行是个好主意。

数字列表以数字或字母和罗马数字开头。要自动生成编号,#可用作列表项:

Number Lists 

1\.  Leading digit or letter. 

#.  Auto-numbering with #. 

#.  Looks like this. 

我们可以使用缩进规则在列表中创建列表。它可能很复杂,docutilsRST 解析器通常会理解您的意思。

块引号只是缩进文本:

Here's a paragraph with a cool quote. 

    Cool quotes might include a tip. 

Here's another paragraph. 

代码样本用::双冒号表示;它们是缩进的,以空行结尾。虽然::可以在一行的末尾,也可以单独在一行上,但将::放在单独的一行上可以稍微容易地找到代码样本。

下面是一个代码示例:

Here's an example:
:: 

    x = Deck() 
    first_card = x.pop() 

This shows two lines of code. It will be distinguished from surrounding text. 

docutils解析器还将定位doctest材料,并将其放在一边进行特殊格式化,类似于代码块。它们以>>>开头,以空行结尾。

以下是来自doctest的一些示例输出:

Here's an example:
::

    >>> x = Unsorted_Deck() 
    >>> x.pop() 
    'A♣' 

This shows how the :class:`Unsorted_Deck` class works.

测试输出末尾的空行是必不可少的,很容易被忽略。当 doctest 错误消息包含周围的文本时,这意味着需要额外的空行

我们可以注释文本的一种方法是提供内联标记,以识别应该以不同方式强调的内容。它可能是代码,或者是一个重要的单词,或者是一个交叉引用。在下一节中,我们将展示如何在 RST 中使用内联标记。

RST 内联标记

在大多数文本块中,我们可以包含内联标记。我们不能在代码示例或doctest块中包含内联标记。请注意,我们也不能嵌套内联标记。

RST 内联标记包括多种常见的文本 ASCII 处理方法。例如,我们有*emphasis***strong emphasis**,它们通常分别产生斜体和粗体。我们可能希望强调文本块中的代码段;我们使用pyliteral强制使用单间距字体。

我们还可以将交叉引用作为内联标记。尾随的_表示引用,它指向前面的单词;前导_表示一个目标,它指向下面的单词。例如,我们可能有some phrase`_`作为参考。然后我们可以使用`_`some phrase作为该参考的目标。我们不需要为章节标题提供明确的目标:我们可以参考``This Is A Heading_,因为所有章节标题都已经定义为目标。对于 HTML 输出,这将生成预期的``标记。对于 PDF 输出,将生成文本内链接。

我们不能嵌套内联标记。几乎不需要嵌套的内联标记;太多的排版技巧会导致视觉混乱。如果我们的作品对排版如此敏感,我们可能应该直接使用乳胶。

内联标记也可以有显式的角色指示符;这是:role:,后面是text。简单 RST 的角色相对较少。我们可以使用:code:some code``来更明确地说明文本中是否存在代码示例。当我们看狮身人面像时,有许多角色指标。使用显式角色可以提供大量语义信息。

在做数学更复杂的事情时,我们可以使用 LaTeX 数学排版功能。这使用了:math:角色;看起来是这样的::math:a=\pi r^2``

角色是开放的。我们可以为添加新角色的 docutils 提供配置。这是通过 Sphinx 等工具实现的

除了内联角色定义之外,RST 还有许多指令。在下一节中,我们将研究这些指令。

RST 指令

RST 还包括指令。指令写在以..开头的块中。指令可能包含缩进的内容。它也可能有参数。RST 有大量的指令,我们可以使用它们来创建更复杂的文档。对于 docstring 准备,我们很少使用超过几个可用的指令。这些指令是开放式的;Sphinx 等工具将添加指令以生成更复杂的文档。

三个常用的指令是imagecsv-tablemath。如果我们有一个图像应该是我们文档的一部分,我们可以通过以下方式将其包括在内:

..  image:: media/some_file.png 
    :width: 6in 

我们将文件命名为media/some_file.png。我们还为它提供了一个width参数,以确保我们的图像符合文档页面布局。我们还可以使用许多其他参数来调整图像的显示。

  • :align::我们可以提供topmiddlebottomleftcenterright等关键词。此值将提供给 HTML<img>标记的align属性。
  • :alt::这是图像的备选文本。此值将提供给 HTML<img>标记的alt属性。
  • :height::这是图像的高度。
  • :scale::这是可以提供的比例因子,而不是高度和宽度。
  • :width::这是图像的宽度。
  • :target::这是图像的目标超链接。这可以是完整的 URI,也可以是``name_表单的 RST 引用。

对于高度和宽度,可以使用 CSS 中可用的任何长度单位。其中包括em(元素字体的高度)、ex(字母x的高度)、px(像素)以及绝对大小;incmmmpt(点)和pc(pica)。

我们可以以下列方式在我们的文件中包括一个表格:

..  csv-table:: Suits 
    :header: symbol, name 

    "'♣'", Clubs 
    "'♦'", Diamonds 
    "'♥'", Hearts 
    "'♠'", Spades 

这允许我们准备数据,这些数据将以简单的 CSV 表示法成为一个复杂的 HTML 表。我们可以使用math指令得到更复杂的公式:

..  math:: 

    \textbf{O}(2^n)

这允许我们编写更大的 LaTeX 数学来创建一个单独的方程式。这些文件也可以编号和交叉引用。注意公式周围的空行和压痕;这些对于帮助 RST 工具定位要作为数学处理的相关文本至关重要。

还有许多其他 RST 标记技术。在下一节中,我们将提出一些方法来学习如何对文本进行注释,以便对其进行正确的格式设置和索引。

学习 RST

在 RST 中建立技能的一种方法是安装docutils并使用rst2html.py脚本解析 RST 文档并将其转换为 HTML 页面。一个简单的实践文档可以很容易地向我们展示各种 RST 特性。

项目的所有需求、体系结构和文档都可以使用 RST 编写并转换为 HTML 或 LaTeX。在 RST 中编写用户故事并将这些文件放到一个可以组织和重组的目录中相对便宜,因为故事是经过整理、投入开发和实现的。更复杂的工具可能不会比docutils更有价值。

使用纯文本文件和 RST 标记的优点是,我们可以轻松地与源代码并行管理文档。我们没有使用专有的字处理文件格式。我们没有使用冗长冗长的 HTML 或 XML 标记,这些标记必须经过压缩才能实用。我们只是在源代码中存储更多的文本。

如果我们使用 RST 创建文档,我们还可以使用rst2latex.py脚本创建.tex文件,我们可以通过 LaTeX 工具集运行该文件来创建 postscript 或 PDF 文档。这需要一个 LaTeX 工具集,通常使用TeXLive发行版。参见http://www.tug.org/texlive/ 提供一整套工具,将 TeX 转换为优雅的最终文档。TeXLive 包含 pdfTeX 工具,可用于将 LaTeX 输出转换为 PDF 文件。

在下一节中,我们将看到如何编写有效的 docstring。

编写有效的文档字符串

在编写 docstring 时,我们需要关注观众需要的基本信息。当我们考虑使用库模块时,我们需要知道什么?无论我们问什么问题,都意味着其他程序员经常会有类似的问题。当我们编写 docstring 时,有两个边界应该保持在内部:

  • 最好避免抽象概述、高级需求、用户故事或与代码无关的背景。我们应该在另一份文件中提供背景资料。Sphinx 等工具可以在单个文档中组合背景材料和代码。
  • 最好避免过于详细的工作原理实现琐事。代码很容易获得,所以在文档中重述代码是没有意义的。如果代码太模糊,也许应该重写以使其更清晰。

也许开发人员想要的最重要的事情是一个如何使用 Python 对象的工作示例。RST::文本块是这些示例的主干。

我们通常以以下方式编写 RST 代码示例:

Here's an example:: 

    d = Deck() 
    c = d.pop() 

双冒号::位于缩进块之前。缩进的块被 RST 解析器识别为代码,并将直接传递到最终文档。

除了示例之外,形式化 API 也很重要。在后面的部分中,我们将介绍几种 API 定义技术。这些依赖于 RST字段列表语法。它非常简单,这使得它非常灵活。

一旦我们通过了这个示例和 API,就会有很多其他东西争夺第三名。我们还需要写什么取决于上下文。似乎有三种情况:

  • 文件,包括包和模块:在这些情况下,我们提供了对模块、类或函数定义集合的概述或介绍。我们需要提供文件中各种元素的简单路线图或概述。在模块相对较小的情况下,我们可能会在此级别提供 doctest 和代码示例。
  • 类,包括方法函数:我们经常在这里提供解释类 API 的代码示例和doctest块。因为类可能是有状态的,并且可能具有相对复杂的 API,所以我们可能需要提供相当长的文档。单个方法函数通常会有详细的文档。
  • 函数:我们可以提供解释函数的代码示例和doctest块。因为函数通常是无状态的,所以我们可能有一个相对简单的 API。在某些情况下,我们可能会避免使用更复杂的 RST 标记,而将重点放在help()函数的文档上。

我们将详细了解这些广泛而模糊的文档上下文。

编写文件级 docstring,包括模块和包

包或模块的目的是包含许多元素。包包含模块以及类、全局变量和函数。模块包含类、全局变量和函数。这些容器上的顶级 docstring 可以作为路线图来解释包或模块的一般特性。详细信息委托给各个类或函数。

我们可能有一个模块 docstring,它看起来像以下代码:

    Blackjack Cards and Decks
        =========================

        This module contains a definition of :class:`Card`, 
        :class:`Deck` and :class:`Shoe` suitable for Blackjack.

        The :class:`Card` class hierarchy
        ---------------------------------

        The :class:`Card` class hierarchy includes the following class definitions.

        :class:`Card` is the superclass as well as being the class for number cards.
        :class:`FaceCard` defines face cards: J, Q and K.
        :class:`AceCard` defines the Ace. This is special in Blackjack because it creates a soft total for a hand.

        We create cards using the :func:`card` factory function to create the proper
        :class:`Card` subclass instances from a rank and suit.

        The :class:`Suit` enumeration has all of the Suit instances.

        ::

            >>> from ch20_ex1 import cards
            >>> ace_clubs= cards.card( 1, cards.suits[0] )
            >>> ace_clubs
            'A♣'
            >>> ace_diamonds= cards.card( 1, cards.suits[1] )
            >>> ace_clubs.rank ==  ace_diamonds.rank
            True

        The :class:`Deck` and :class:`Shoe` class hierarchy
        ---------------------------------------------------

        The basic :class:`Deck` creates a single 52-card deck. 
The :class:`Shoe` subclass creates a given number of decks. 
A :class:`Deck`         can be shuffled before the cards can be 
extracted with the :meth:`pop` method. 
A :class:`Shoe` must be shuffled and
        *burned*. The burn operation sequesters a random number of cards 
based on a mean and standard deviation. The mean is         a number of 
cards (52 is the default.) 
The standard deviation for the burn is 
also given as a number of cards (2 is         the default.)

本文档字符串中的大部分文本提供了本模块内容的路线图。它描述了类层次结构,使得定位相关类稍微容易一些。对类的引用使用 RST 内联标记。引用后面有一个角色前缀;例如,:class:Card``生成的文本将是指向Card类定义的超链接。我们稍后将查看这些参考资料。在纯 Python 环境中,这些简单的引用工作得很好;在多语言环境中,或者在 Python 之外使用 Sphinx 时,角色名有一些重要的变化。

docstring 包括一个基于doctestcard()工厂函数的简单示例。这将此功能作为模块整体的一个重要功能进行宣传。提供Shoe类的doctest解释可能是有意义的,因为这可能是本模块最重要的部分。

此 docstring 包含一些内联 RST 标记,用于将类名放入单间距字体中。章节标题在下划线处加上===---行。RST 解析器可以确定以===划线的标题是以---划线的标题的父项。

我们将在后面的部分中介绍如何使用 Sphinx 生成文档。Sphinx 将利用 RST 标记生成美观的 HTML 文档。

现在,让我们看看如何在 RST 标记中编写 API 细节。

在 RST 标记中编写 API 详细信息

使用 RST 标记的好处之一是我们可以提供正式的 API 文档。API 参数和返回值使用 RST字段列表进行格式化。通常,字段列表具有以下形式:

:field1: some value 
:field2: another value 

字段列表是一系列字段标签(如:label:)以及与该标签关联的值。标签通常较短,值可以根据需要任意长。字段列表还用于为指令提供参数。

我们将使用 RST 字段列表语法的扩展形式来编写 API 文档。我们将扩展字段名,使其成为一个多部分项。我们将添加带有关键字的前缀,如paramtype。前缀后面是参数的名称。

有几个字段前缀。我们可以使用以下任意一种:paramparameterargargumentkeykeyword。例如,我们可以编写以下代码:

:param rank: Numeric rank of the card 
:param suit: Suit of the card 

位置参数一般使用param(或parameter),关键字参数一般使用key(或keyword)。这些字段列表定义将收集在缩进部分中。Sphinx 工具还将比较文档中的名称与函数参数列表中的名称,以确保它们匹配。

我们还可以使用type作为前缀来定义参数的类型:

:type rank: integer in the range 1-13\. 

虽然这可能会有所帮助,但在函数和方法定义中使用适当的类型提示要好得多。Sphinx 和其他工具使用类型提示。也由mypy进行检查。

对于返回值的函数,我们应该描述结果。我们可以使用字段标签returnsreturn汇总返回值

:returns: soft total for this card

此外,我们还应该包括有关此函数特有的异常的信息。这个字段有四个别名:raisesraiseexceptexception。我们将编写以下代码:

:raises TypeError: rank value not in range(1, 14). 

我们还可以描述类的属性。为此,我们可以使用varivarcvar。我们可以编写以下代码:

:ivar soft: soft points for this card; usually hard points, except for aces. 
:ivar hard: hard points for this card; usually the rank, except for face cards. 

我们应该使用ivar作为实例变量,使用cvar作为类变量。但是,最终的 HTML 输出没有明显的区别。

这些字段列表构造用于为类、类方法和独立函数准备 docstring。我们将在后面的部分中查看每个案例。

现在,让我们看看如何编写类和方法函数 docstrings。

编写类和方法函数 docstring

一个类通常包含许多元素,包括属性和方法函数。有状态类也可能具有相对复杂的 API。对象将被创建,状态发生变化,并且可能在生命结束时被垃圾收集。我们可能希望在类 docstring 或方法函数 docstring 中描述这些状态的一些(或全部)更改。

我们将使用字段列表技术在整个类 docstring 中记录类变量。这通常侧重于使用:ivar variable::cvar variable::var variable:字段列表项。

每个方法函数还将使用字段列表定义参数,并返回每个方法函数引发的值和异常。下面是我们如何开始为类和方法函数编写带有 docstring 的类:

    class     Card:
        """
            Definition of a numeric rank playing card.
            Subclasses will define :py:class:`FaceCard` and :py:class:`AceCard`.

                    :ivar         rank: int rank of the card
                    :ivar         suit: Suit suit of the card
                    :ivar         hard: int Hard point total for a card
                    :ivar         soft: int Soft total; same as hard for all cards except Aces.
            """

                    def         __init__    (
            self    , rank:     int    , suit: Suit, hard:     int    , soft: Optional[    int    ] =     None
                ) ->     None    :
            """Define the values for this card.

                        :param         rank: Numeric rank in the range 1-13.
                        :param         suit: Suit object from :class:`Suits`
                        :param         hard: Hard point total (or 10 for FaceCard or 1 for AceCard)
                        :param         soft: The soft total for AceCard, otherwise defaults to hard.
                """
                        self    .rank = rank
            self    .suit = suit
            self    .hard = hard
            self    .soft = soft     if     soft     is not None else     hard

        def         __str__    (    self    ) ->     str    :
            return         f"        {        self    .rank    }{        self    .suit    }        "

                    def         __repr__    (    self    ) ->     str    :
            return         f"        {        self    .__class__.    __name__        }        (rank=        {        self    .rank    }        , suit=        {        self    .suit    }        )"

当我们在 docstring 中包含这种 RST 标记时,Sphinx 之类的工具可以格式化非常漂亮的 HTML 输出。我们为您提供了实例变量的类级文档以及其中一个方法函数的参数的方法级文档。

本例使用文本:py:class:Card``生成对类别卡片的引用。这个标记中的角色名看起来很复杂:py:class:,有助于区分 Python 语言域。在复杂的项目中,可能有多个语言域,角色名称可以反映域的多样性。

当我们使用help(Card)查看这个类时,RST 标记将可见。这并不太令人反感,因为它在语义上是有意义的。这指出了我们可能需要在help()文本和斯芬克斯文件之间取得平衡。

写入函数 docstring

可以使用字段列表格式化函数 docstring,以定义参数并返回值和引发的异常。下面是一个包含 docstring 的函数示例:

    def card(rank: int, suit: Suit) -> Card:
    """
    Create a :py:class:`Card` instance from rank and suit.

    :param suit: Suit object
    :param rank: Numeric rank in the range 1-13
    :returns: :py:class:`Card` instance
    :raises TypeError: rank out of range

    >>> from Chapter_20.ch20_ex1 import card
    >>> str(card(3, Suit.Heart))
    '3♥'
    >>> str(card(1, Suit.Heart))
    'A♥'
    """
            if     rank ==     1    :
            return     AceCard(rank, suit,     1    ,     11    )
        elif         2     <= rank <     11    :
            return     Card(rank, suit, rank)
        elif         11     <= rank <     14    :
            return     FaceCard(rank, suit,     10    )
        else    :
            raise         TypeError

此函数的 docstring 包括参数定义、返回值和引发的异常。有四个单独的字段列表项用于形式化 API。我们还包括了一个doctest序列。当我们在 Sphinx 中记录这个模块时,我们将得到非常漂亮的 HTML 输出。此外,我们可以使用doctest工具来确认函数与简单测试用例匹配。

斯芬克斯将稍微扩展类型提示。前面的代码使用源代码card(rank: int, suit: Suit) -> Card。这将在 Sphinx 创建的 HTML 页面中扩展到ch20_ex1.card(rank: int, suit: ch20_ex1.Suit) -> ch20_ex1.Card。类名中添加了前缀,以帮助读者理解软件。

更复杂的标记技术

还有一些附加的标记技术可以使文档更易于阅读。特别是,我们经常希望类定义之间有有用的交叉引用。我们可能还需要文档中各部分和主题之间的交叉引用。

RST(即没有 Sphinx)中,我们需要提供引用文档不同部分的正确 URL。我们有三种参考:

  • 暗指章节标题:我们可以使用``Some Heading_来指代`Some Heading`章节。这将适用于 docutils 识别的所有标题。
  • 明确引用目标:我们可以使用target_引用_target在文档中的位置。
  • 文档间引用:我们必须创建一个完整的 URL,明确引用章节标题。Docutils 会将章节标题翻译成所有小写字母,并将标点符号替换为-。这允许我们在外部文档中创建对章节标题的引用,如:``Design file:build.py.html#design_

当我们使用 Sphinx 时,我们获得了更多的文档间、交叉引用功能。这些对基本 RST 的扩展允许我们避免尝试编写详细的 URL。为此,我们将在标题前面包含一个目标标签,并使用:ref:label``语法引用该标签。

我们可能有一个带有某些 RST 的文档,如以下代码段所示:

.. _user_stories:
User Stories
============

The user generally has three tasks: customize the simulation's parameters, run a simulation, and analyze the results of a simulation.

请注意,标签以_开头,表示它是一个目标;标签本身跟随前面的_。行开头的..:将此作为 RST 的指令。RST 过程的规则明确规定忽略未知指令。RST 可以由许多工具处理,并且工具特定的指令很常见。一个工具会悄悄地忽略针对另一个工具的指令。由于这个原因,这个标签作为指示的技术工作得非常优雅。

在另一份文件中,我们可能有:ref:user_stories``。注意缺少一个前导的_;这是对标签的引用,而不是标签的定义。Sphinx 跟踪所有标签和相关的标题,以便能够创建正确的 HTML 引用。

现在,让我们看看如何使用 Sphinx 生成文档。

使用 Sphinx 生成文档

Sphinx 工具以各种格式生成非常好看的文档。它可以轻松地将源代码和外部文件中的文档与其他设计说明、需求或背景结合起来。

斯芬克斯工具可在找到 http://sphinx-doc.org 。下载可能会变得复杂,因为 Sphinx 依赖于其他几个项目。斯芬克斯教程非常出色。

大多数项目将使用sphinx-quickstart创建初始文件集。文件可用后,可以添加详细信息。为最终确定文件,将使用sphinx-build程序。

通常,运行sphinx-build是通过make程序处理的,这稍微简化了 Sphinx 的命令行使用。在某些情况下,make可能不可用。

  • macOS 用户在默认情况下不会有使可用。它是苹果 XCode 开发工具包的一部分。虽然下载量很大,但 XCode 工具易于安装和使用。有些人喜欢用自制软件来安装make,而不是 XCode。有关自制工具的信息,请访问https://brew.shbrew install make命令将为 macOS 创建一个有用的 make 实用程序
  • 对于窗口用户,sphinx-quickstart将创建一个行为类似于make实用程序的make.bat脚本。

不需要make实用程序,但它很方便。我们总是可以直接使用sphinx-build

让我们在下一节学习如何使用 Sphinx quickstart。

使用 Sphinx quickstart

sphinx-quickstart的便利功能是,它通过一个交互式问答会话填充相当复杂的config.py文件;我们强调了一些默认值似乎不是最佳值的响应。

对于更复杂的项目,从长远来看,将文档与工作代码分离会更简单。通常最好在整个项目树中创建一个doc目录:

Enter the root path for documentation. > Root path for the documentation [.]: docs 

对于非常小的文档,可以将源代码和 HTML 交织在一起。对于较大的文档,特别是可能需要生成 LaTeX 和 PDF 的文档,将这些文件与文档的 HTML 版本分开是很方便的:

You have two options for placing the build directory for Sphinx output. 
Either, you use a directory "_build" within the root path, or you separate 
"source" and "build" directories within the root path. 
> Separate source and build directories (y/N) [n]: n 

下一批问题确定了特定的附加组件。它从以下注释开始:

Please indicate if you want to use one of the following Sphinx extensions: 

我们将推荐一组似乎对一般 Python 开发最有用的附加组件。对于首次使用 Sphinx 的用户来说,这将足以开始使用并生成优秀的文档。显然,特定的项目需求和目标将凌驾于这些通用建议之上。

我们几乎总是希望包含autodoc特性,以便从 docstring 生成文档。如果我们使用 Sphinx 在 Python 编程之外生成文档,我们可能需要关闭autodoc

> autodoc: automatically insert docstrings from modules (y/N) [n]: y 

如果我们有doctest的例子,我们可以让斯芬克斯为我们运行 doctest。对于大多数测试通过doctest完成的小型项目,这可能非常方便。对于较大的项目,我们通常会有一个包含 doctest 的单元测试脚本。通过 Sphinx 执行 doctest,以及通过正式的单元测试,仍然是一个好主意:

> doctest: automatically test code snippets in doctest blocks (y/N) [n]: y 

一个成熟的开发工作可能有许多密切相关的项目;密切相关的项目可能有多个相关的 Sphinx 文档目录:

> intersphinx: link between Sphinx documentation of different projects (y/N) [n]: 

todo扩展允许我们在 docstring 中包含.. todo::指令。然后,我们可以添加一个特殊的.. todolist::指令,在文档中创建一个正式的待办事项列表:

> todo: write "todo" entries that can be shown or hidden on build (y/N) [n]: 

覆盖率报告可以是一个方便的质量保证指标:

> coverage: checks for documentation coverage (y/N) [n]: 

对于涉及任何数学的项目,使用 LaTeX 工具集可以将数学很好地打印为图形图像并包含在 HTML 中。它还将原始数学保留在乳胶输出中。MathJax 是一个基于 web 的 JavaScript 库,其工作方式如下:

> pngmath: include math, rendered as PNG images (y/N) [n]: y 
> mathjax: include math, rendered in the browser by MathJax (y/N) [n]: 

对于非常复杂的项目,我们可能需要生成变体文档:

> ifconfig: conditional inclusion of content based on config values (y/N) [n]: 

大多数应用程序文档描述 API。我们应该包括autodocviewcode特性。viewcode选项允许读者查看源代码,以便详细了解实现:

> viewcode: include links to the source code of documented Python objects (y/N) [n]: y 

最后,在使用 GitHub 时,它有助于包含一个特殊文件,以防止使用 Jekyll 工具呈现页面。

> githubpages: create .nojekyll file to publish the document on GitHub pages (y/n) [n]: y

autodocdoctest特性意味着我们可以专注于在代码中编写 docstring。我们只需要编写非常小的 Sphinx 文档文件来提取文档字符串信息。对于一些开发人员来说,专注于代码的能力减少了与编写文档相关的恐惧因素。

在下一节中,我们将看到如何编写 Sphinx 文档。

编写斯芬克斯文档

从文档的占位符大纲开始通常会很有帮助,这些占位符将随着软件开发的进行而积累。一种可能有用的结构是基于架构的 4+1 视图。有关更多信息,请参阅 Packt Publishing 提供的软件架构师手册

我们可以在我们的index.html根目录下创建五个顶级文档:user_storieslogicalprocessimplementationphysical。每一个都需要一个 RST 标题段落,但不需要更多。

然后,我们可以在默认情况下更新 Sphinxindex.rst文件中生成的.. toctree::指令:

.. Mastering OO Python documentation master file, created by 
   sphinx-quickstart on Fri Jan 31 09:21:55 2014\. 
   You can adapt this file completely to your liking, but it should at least 
   contain the root `toctree` directive. 

Welcome to Mastering OO Python's documentation! 
=============================================== 

Contents: 

.. toctree:: 
   :maxdepth: 2 

   user_stories 
   logical 
   process 
   implementation 
   physical 

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

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

一旦我们有了顶层结构,我们就可以使用make命令来构建文档:

make doctest html 

这将运行我们的 doctest,如果所有测试都通过,它将创建 HTML 文档。

在下一节中,我们将查看如何填写应用程序软件各种视图的详细信息。

填写 4+1 视图以获取文档

随着开发的进行,4+1 视图可用于组织累积的细节。其思想是收集代码中 docstring 窄焦点之外的信息。

user_stories.rst文档是我们收集用户故事、需求和其他高级背景说明的地方。如果用户故事变得复杂,这可能会演变成目录树。

logical.rst文档是收集类、模块和包的初始 OO 设计的地方。这应该是我们设计思想的起源。它可能包含备选方案、注释、数学背景、正确性证明和逻辑软件设计图。对于设计相对清晰的相对简单的项目,这可能仍然是空的。对于复杂的项目,这可能描述一些复杂的分析和设计,作为实施的背景或理由。

最终的 OO 设计将是属于implementation.rst文件的 Python 模块和类。我们将更详细地了解这一点,因为这将成为我们的 API 文档。这一部分将直接基于我们的 Python 代码和 RST 标记 docstrings。

process.rst文档可以收集有关动态运行时行为的信息。这将包括并发、分发和集成等主题。它还可能包含有关性能和可伸缩性的信息。这里可以描述所使用的网络设计和协议。

对于较小的应用,工艺文件中应该包含哪些材料可能并不十分清楚。本文件可能与逻辑设计和总体架构信息重叠。当有疑问时,我们必须根据受众对信息的需求力求清晰。对于一些用户来说,许多小文档是有帮助的。对于其他用户,最好使用单个大型文档。

physical.rst文件是可以记录部署细节的地方。此处将介绍配置详细信息,特别是环境变量、配置文件格式详细信息、可用记录器名称以及管理和支持所需的其他信息。这还可能包括配置信息,例如服务器名称、IP 地址、帐户名、目录路径和相关注释。在某些组织中,管理员可能会觉得其中一些详细信息不适用于一般的软件文档。

在下一节中,我们将看到如何编写实现文档。

编写执行文件

implementation.rst文档可以基于automodule创建文档。以下是implementation.rst文档的开始方式:

    Implementation
        ==============

    Here's a reference to the     `inception document <_static/inception_doc/index.html>`_

    Here's a reference to the     :ref:        `user_story`    

    The ch20_ex1 module
        -------------------

    ..      automodule::     ch20_ex1
        :members:
            :undoc-members:
            :special-members:

        Some Other Module
        -----------------

    We'd have an     ``..  automodule::``     directive here, too.

我们使用了两种 RST 标题:一个单一的顶级标题和两个副标题。RST 推断父对象和子对象之间的关系。在本例中,我们使用===作为父标题(也是标题),使用---作为副标题。

我们已经向您提供了一个明确的参考文件,该文件作为inception_doc复制到_static目录中。我们创建了一个复杂的 RST 链接,从单词初始文档到实际文档的index.html文件。

:ref:user_story是对另一章节的内部交叉引用。Sphinx 跟踪所有前面有用作标签的指令的标题和标题。在这种情况下,标题前面的一行将显示带有`.. _user_story:`的标题。`_user_story`是 RST 目标;它必须以`_`开头。`:ref:`user_story链接将引用目标后面的标题。我们可以更改标题,HTML 将被正确更新。

在这两个副标题中,我们使用 Sphinx.. automodule::指令从两个模块中提取文档字符串。我们为您提供了 automodule 指令的三个参数:

  • :members::包括模块的所有成员。我们可以列出显式成员类和函数,而不是列出所有成员。
  • :undoc-members::这包括缺少适当文档字符串的成员。这在开始开发时很方便;我们仍将获得一些 API 信息,但这将是最小的。
  • :special-members::包括特殊方法名成员,默认情况下 Sphinx 文档中不包括这些成员。

这给了我们一个相对完整的视图。如果我们省略所有这些参数,:undoc-members::special-members:,我们将得到一个更小、更集中的文档。

我们的implementation.rst可以随着项目的发展而发展。我们将在模块完成后添加automodule引用。

.. automodule::指令的组织可以为我们提供一个有用的路线图或模块或包的复杂集合的概述。花一点时间组织演示文稿,以便向我们展示软件组件是如何协同工作的,这比大量的废话更有价值。重点不是要创造伟大的叙事文学;重点是为其他开发人员提供指导。

创建斯芬克斯交叉引用

Sphinx 通过 RST 扩展了交叉参考技术。最重要的交叉引用功能是能够直接引用特定的 Python 代码特性。它们使用:role:text``语法的内联 RST 标记。在这种情况下,大量额外的角色是斯芬克斯的一部分。

我们提供以下类型的交叉参考角色:

  • :py:mod:some_module``语法将生成指向此模块或包定义的链接。
  • :py:func:some_function``语法将生成指向函数定义的链接。可以使用带有module.function或`package.module.function`的限定名称。
  • :py:data:variable和`:py:const:`variable语法将生成指向模块变量的链接,该模块变量由.. py:data:: variable指令定义。常数只是一个不应更改的变量。
  • :py:class:some_class``语法将链接到类定义。可以使用module.class等限定名称。
  • :py:meth:class.method``语法将链接到方法定义。
  • :py:attr:class.attribute``语法将链接到使用.. py:attribute:: name指令定义的属性。
  • :py:exc:exception``语法将链接到已定义的异常。
  • :py:obj:some_object``语法可以创建指向对象的通用链接。

如果我们在文档字符串中使用pySomeClass,我们将以单间距字体获得类名。如果我们使用:py:class:SomeClass``,我们会得到一个到类定义的适当链接,这通常会更有帮助。

每个角色都有:py:前缀,因为 Sphinx 可以用来编写除 Python 之外的其他语言的文档。通过在每个角色上使用:py:前缀,Sphinx 可以提供适当的语法添加和突出显示。

下面是一个 docstring,其中包含对其他类和异常的显式交叉引用:

    def     card(rank:     int    , suit: Suit) -> Card:
        """
            Create a :py:class:`Card` instance from rank and suit.
            Can raise :py:exc:`TypeError` for ranks out of the range 1 to 13, inclusive.

                    :param         suit: Suit object
                    :param         rank: Numeric rank in the range 1-13
                    :returns        : :py:class:`Card` instance
                    :raises         TypeError: rank out of range        
            """

通过使用:py:class:Card``而不是pyCard,我们能够在这个注释块和Card类的定义之间创建显式链接。虽然包含角色前缀会有一点键入开销,但由此产生的交叉引用网络会产生非常有用的文档。

当我们的项目变大时,我们可能需要使用目录来组织文件。在下一节中,我们将研究将长而扁平的文件序列重构到目录中以实现某种结构和组织的技术。

将 Sphinx 文件重构到目录中

对于较大的项目,我们需要使用目录而不是简单的文件。通常,项目会从几个简单的文件扩展到更复杂的结构,其中的文件由目录替换。在本例中,我们将执行以下步骤将 RST 文件重构到包含 RST 文件的目录中:

  1. 添加目录–implementation,例如。
  2. 将原件implementation.rst移至implementation/index.rst
  3. 更改原始index.rst文件。将.. toctree::指令切换到参考implementation/index而不是implementation

然后,我们可以使用implementation/index.rst文件中的.. toctree::指令在implementation目录中工作,以包括该目录中的其他文件。

当我们的文档被分成简单文本文件的简单目录时,我们可以编辑小的、有重点的文件。单个开发人员可以做出重大贡献,而不会在尝试编辑字处理文档时遇到任何文件共享冲突。

处理遗留文档

软件开发项目通常从描述项目原因的文档集合开始。这些文件可以称为初始文件,因为它们描述了项目初始阶段。这些文件可能包括证明项目合理性的备忘录和演示文稿。它们通常描述最基本的用户故事。

理想情况下,这些初始文档已经是文本文件。实际上,他们几乎从不发短信。通常,inception 文档是幻灯片格式,可能是 Microsoft PowerPoint、Apple Keynote 或 Google 幻灯片。这些文档将文本与图表混合在一起,使它们成为一种挑战。

目标是创建一个文本文件,该文件可以作为基于 RST 的文档的一部分。例如,许多文字处理器可以将文档另存为文本。一些文字处理器可以产生降价标记;这很容易转换为 RST。在某些情况下,我们可能希望使用 pandoc 之类的工具从原始文档中提取 RST 标记。这使我们能够将文档导入到项目中,并通过 Sphinx 工具使用它。有关 pandoc 的更多信息,请参阅https://pandoc.org

其中一个困难的例子是一个项目,其中一些初始文档是幻灯片。图表和图像是内容的一流部分,没有方便的文本表示。在这些情况下,有时最好将演示文稿导出为 HTML 文档,并将其放入 Sphinxdoc/source/_static目录中。这将允许我们通过``Inception <_static/inception_doc/index.html>_形式的简单 RST 链接将原始材料整合到斯芬克斯中。

在某些情况下,可以使用基于 web 的交互式工具(如 Trello)来管理项目或用户故事。在这种情况下,初始文档和背景文档可以通过以下表单的简单 URL 引用进行处理:``Background http://someservice/path/to/page.html_

在下一节中,我们将看到如何编写文档。

编写文档

软件质量的一个重要部分来自于注意到软件开发的最终产品远不止代码。正如我们在第 17 章为可测试性设计中所述,不能使用不可信的代码。在那一章中,我们建议测试对于建立信任至关重要。我们想概括一下。除了详细的测试之外,还有一些其他质量属性使代码可用。可信度支持可用性。

值得信赖的代码有许多方面:

  • 我们理解用例
  • 我们了解数据模型和处理模型
  • 我们理解测试用例

当我们研究更多的技术质量属性时,我们发现这些属性实际上是关于理解的。例如,调试似乎意味着我们可以确认我们对应用程序如何工作的理解。可审核性似乎还意味着我们可以通过查看特定示例来确认我们对处理的理解,以表明它们按预期工作。

文档创建信任。有关软件质量的更多信息,请从这里开始:https://s-cube-network.eu/km/qrm/index.html 。关于软件质量有很多东西需要学习;这是一个很大的区域,这只是一个很小的方面

创建详细文档的一种方法是从同一源生成最终的人类可读文档和工作源代码。这就是识字编程的思想,这是下一节的主题。

识字编程

文档代码分离的想法可以看作是人为的区分。从历史上看,我们在代码之外编写文档是因为编程语言相对不透明,偏向于高效编译,而不是清晰的说明。已经尝试了不同的技术来缩短工作代码和与代码相关的文档之间的距离。例如,嵌入更复杂的注释是缩短代码和注释之间距离的一项长期传统。Python 在包、模块、类和函数中包含了一个正式的 docstring,从而更进一步。

Donald Knuth 开创了软件开发的识字编程方法。其思想是,单个源文档可以生成高效的代码和外观良好的文档。对于面向机器的汇编语言和 C 等语言,从源语言(强调翻译的符号)转向强调清晰阐述的文档还有一个额外的好处。此外,一些有文化的编程语言充当高级编程语言;这可能适用于 C 或 Pascal,但对 Python 毫无帮助。

识字编程是促进对代码的深入理解。就 Python 而言,源代码一开始非常可读。使 Python 程序易懂不需要复杂的识字编程。事实上,Python 识字编程的主要好处是以比简单 Unicode 文本更可读的形式携带更深入的设计和用例信息。

更多信息,请参考http://www.literateprogramming.comhttp://xml.coverpages.org/xmlLitProg.html 。唐纳德·克努特(Donald Knuth)的书识字编程是这一主题的开创性书名。

识字编程的用例

创建识字计划时有两个基本目标:

  • 工作程序:这是从源文件中提取的代码,为编译器或解释器准备。
  • 易于阅读的文档:这是说明,加上代码,再加上为演示准备的任何有用的标记。该文档可以是 HTML 格式,可以随时查看,也可以是 RST 格式,我们可以使用 docutilsrst2html.py将其转换为 HTML。或者,它可以在 LaTeX 中,我们通过 LaTeX 处理器运行它来创建 PDF 文档。

工作程序的目标意味着我们的识字编程文档将涵盖整个源代码文件套件。虽然这看起来令人望而生畏,但我们必须记住,组织良好的代码片段并不需要很多复杂的挥手动作;在 Python 中,代码本身可以是清晰而有意义的。

易于阅读的文档目标意味着我们希望生成一个使用单一字体以外的其他字体的文档。虽然大多数代码都是用单间距字体编写的,但在我们看来这并不是最简单的。基本 Unicode 字符集也不包括有帮助的字体变体,如粗体或斜体。这些额外的显示细节(字体更改、大小更改、样式更改)经过几个世纪的发展,使文档更具可读性。

在许多情况下,我们的 Python IDE 将对 Python 源代码进行颜色编码。这也很有帮助。书面通信的历史包括许多可以增强可读性的特性,这些特性在使用单一字体的简单 Python 源代码中都不可用。

此外,应围绕问题和解决方案组织一份文档。在许多语言中,代码本身无法遵循清晰的组织,因为它受到语法和编译顺序的纯粹技术考虑的限制。

我们的两个目标归结为两个技术用例:

  • 将原始源文本转换为代码。
  • 将原始源文本转换为最终文档。

在某种程度上,我们可以以某种深刻的方式重构这两个用例。例如,我们可以从代码中提取文档。这就是pydoc模块所做的,但是它不能很好地处理标记。

代码和最终文档这两个版本都可以同构。这是 PyLit 项目采取的方法。最终文档可以通过 docstring 和#注释完全嵌入到 Python 代码中。可以使用::文本块将代码完全嵌入 RST 文档中。

下一节将展示如何使用识字编程工具。

使用识字编程工具

许多识字编程LP工具可用。不同工具的基本要素是将解释与代码分开的高级标记语言,该语言因工具而异。

我们编写的源文件将包含以下三个内容:

  • 包含构成解释和说明的标记的文本
  • Python 中的工作代码
  • 高级标记用于将文本(带有标记)与代码分离

由于 XML 的灵活性,它可以用作识字编程的高级标记。然而,写起来并不容易。有一些工具可以使用基于原始 web(以及后来的 CWeb)工具的类似 LaTeX 的标记。有一些工具可以将 RST 用作高级标记。

因此,选择工具的关键步骤是查看所使用的高级标记。如果我们发现标记易于编写,我们可以轻松地使用它生成源文档。

Python 提出了一个有趣的挑战。因为我们有基于 RST 的工具,比如 Sphinx,所以我们可以有非常好的文档字符串。这就导致了两层文档:

  • 解释和背景。这不符合规定。它提供了有关帮助组织代码的设计决策的支持信息。
  • 参考和 API。这在 Python 文档字符串中。

这导致了一种令人愉快的、渐进的识字编程方法:

  • 首先,我们可以将 RST 标记嵌入到文档字符串中。Sphinx 生成的文档看起来不错,并为实现选择提供了一个简洁的解释。
  • 我们可以超越 docstring 来创建背景文档。这可能包括有关设计决策、体系结构、需求和用户情景的信息。特别是,非功能性质量要求的描述不属于规范范围。
  • 一旦我们开始正式化这个更高层次的设计文档,我们就可以更容易地选择 LP 工具。然后,这个工具将指示我们如何将文档和代码组合成一个单一的、整体的文档结构。我们可以使用 LP 工具提取代码并生成文档。一些 LP 工具也可用于运行测试套件。

我们的目标是创建一个不仅设计良好,而且值得信赖的软件。如前所述,我们通过多种方式建立信任,包括提供整洁、清晰的解释,解释为什么我们的设计是好的。

我们将展示一些使用 PyLit3 的示例。更多信息,请参见https://pypi.org/project/pylit3/3.1.1/ 。从 RST 到 HTML 的转换可以通过 Python 的 docutils 包完成,具体来说就是,rst2html.py脚本。数学排版可以通过 MathJax 或 TeX 工具之一处理。参见https://www.mathjax.orghttps://www.tug.org/texlive/

如果我们使用 PyLit3 之类的工具,我们可能会创建类似以下代码的 RST 文件:

############# 
Combinations 
############# 

..  contents:: 

Definition 
========== 

For some deeper statistical calculations, 
we need the number of combinations of *n* things 
taken *k* at a time, :math:`\binom{n}{k}`. 

..  math:: 

    \binom{n}{k} = \dfrac{n!}{k!(n-k)!} 

The function will use an internal ``fact()`` function because 
we don't need factorial anywhere else in the application. 

We'll rely on a simplistic factorial function without memoization. 

Test Case 
========= 

Here are two simple unit tests for this function provided 
as doctest examples. 

>>> from combo import combinations 
>>> combinations(4,2) 
6 
>>> combinations(8,4) 
70 

Implementation 
=============== 

Here's the essential function definition, with docstring: 
:: 

  def combinations(n: int, k: int) -> int: 
      """Compute :math:`\binom{n}{k}`, the number of 
      combinations of *n* things taken *k* at a time. 

      :param n: integer size of population 
      :param k: groups within the population 
      :returns: :math:`\binom{n}{k}` 
      """ 

An important consideration here is that someone hasn't confused 
the two argument values. 
:: 

      assert k <= n 

Here's the embedded factorial function. It's recursive. The Python 
stack limit is a limitation on the size of numbers we can use. 
:: 

      def fact(a: int) -> int: 
          if a == 0: return 1 
          return a*fact(a-1) 

Here's the final calculation. Note that we're using integer division. 
Otherwise, we'd get an unexpected conversion to float. 
:: 

      return fact(n)//(fact(k)*fact(n-k)) 

这是一个完全用 RST 标记编写的文件。它包含一些解释性的文本,一些正式的数学,甚至一些测试用例。这些为我们提供了支持相关代码部分的附加细节。由于 PyLit 的工作方式,我们将该文件命名为combo.py.txt。我们可以使用此文件执行三项操作:

  • 我们可以使用 PyLit3 以以下方式从该文本文件中提取代码:
python3 -m pylit combo.py.txt 

这将从combo.py.txt创建combo.py。这是一个可以使用的 Python 模块。

  • 我们还可以使用 docutils 将此 RST 格式化为 HTML 页面,该页面以比原始单字体文本更易于阅读的形式提供文档和代码:
rst2html.py combo.py.txt combo.py.html 

这将创建可供浏览的combo.py.html。docutils 将使用mathjax包对数学部分进行排版,从而产生非常漂亮的输出。

  • 此外,我们还可以使用 PyLit 运行doctest并确认该程序确实有效:
python3 -m pylit --doctest combo.py.txt 

这将从代码中提取doctest块,并通过doctest工具运行它们。我们将看到三个测试(导入和两个函数求值)都产生了预期的结果。

由此生成的最终网页类似于以下屏幕截图:

HTML 细节由 RST 到 HTML 工具无缝处理,使我们能够专注于简单的标记和正确的软件。

我们的目标是创建值得信赖的软件。对为什么我们的设计好的一个整洁、清晰的解释是这种信任的一个重要组成部分。通过在单个源文本中并排编写软件和文档,我们可以确保我们的文档是完整的,并对设计决策和软件的总体质量进行合理的审查。一个简单的工具可以从单个源代码中提取工作代码和文档,使我们能够轻松地创建软件和文档。

总结

在本章中,我们介绍了创建可用文档的四种方法。我们可以将信息合并到我们软件中的 docstring 中。我们可以使用pydoc从我们的软件中提取 API 参考信息。我们可以使用狮身人面像创建更复杂、更精细的文档。此外,我们还可以使用有文化的编程工具来创建更深入、更有意义的文档。

设计考虑和权衡

docstring 应该被认为与 Python 源代码的任何其他部分一样重要。这可确保help()功能和pydoc正常工作。与单元测试用例一样,这应该被视为软件的一个强制性元素。

斯芬克斯创建的文档可能非常好看;它将倾向于与 Python 文档并行。我们的目标一直是与 Python 的其他特性无缝集成。使用 Sphinx 往往会为文档源代码和构建引入额外的目录结构。

当我们设计我们的类时,如何描述设计的问题几乎和最终设计本身一样重要。无法快速清晰地解释的软件将被视为不可信。

花时间写一个解释可能会发现隐藏的复杂性或不规则性。在这些情况下,我们可能会重构设计,既不是为了纠正错误,也不是为了提高性能,而是为了更容易解释。解释能力是一个具有巨大价值的质量因素。