18.5 编写文件级别的文档字符串——包括模块和包

    包和模块的目的是包含一系列的元素。包可以包含模块、类、全局变量和函数。模块可以包含类、全局变量和函数。这些容器顶层的文档字符串可以作为描述包或模块的通用特性的蓝图。具体的细节留给各个类或函数来描述。

    我们可能会有一个类似下面的模块文档字符串。

    Blackjack Cards and Decks
    =========================
    This module contains a definition of "Card", "Deck" and "Shoe"
    suitable for Blackjack.
    The "Card" class hierarchy
    ——————————————-
    The "Card" class hierarchy includes the following class definitions.
    "Card" is the superclass as well as being the class for number
    cards.
    "FaceCard" defines face cards: J, Q and K.
    "AceCard" defines the Ace. This is special in Blackjack because it
    creates a soft total for a hand.
    We create cards using the "card()" factory function to create the
    proper
    "Card" subclass instances from a rank and suit.
    The "suits" global variable is a sequence of Suit instances.
    >>> 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 "Deck" and "Shoe" class hierarchy
    —————————————————————-
    The basic "Deck" creates a single 52-card deck. The "Shoe"
    subclass creates a given number of decks. A "Deck" can be shuffled
    before the cards can be extracted with the "pop()" method. A
    "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.)

    这个文档字符串中大部分的文本为这个模块的内容提供了蓝图。它描述了类层次,让我们可以更容易地定位一个相关的类。

    文档字符串包含基于doctestcard()工厂函数的一个简单示例。它指出这个函数是整个模块的一个重要功能。为Shoe类提供doctest说明可能是有意义的,因为这个类可能是这个模块中最重要的部分。

    这个文档字符串包含了一些内联的RST标记,这些标记用等宽字体表示类名。这些节标题以===—-作为下划线。RST解析器可以识别出以===作为下划线的标题是以—-作为下划线的标题的父标题。

    我们会在后面的章节中介绍如何用Sphinx生成文档。Sphinx会使用RST标记生成外观好看的HTML文档。

    18.5.1 用RST标记编写详细的API文档

    使用RST标记的好处是能够提供正式的API文档。API参数和返回值用RST的字段列表格式化。通常,字段的格式如下。

    :field1: some value
    :field2: another value

    字段列表是一系列的字段标签(:label:)和一个与标签相关的值。标签通常很短,根据需要,值可能很长。参数列表也被用于向指令提供参数。

    当字段列表的文本出现在RST文档中时,docutils工具可以创建一个美观的、类似表格的输出。在PDF中,它看起来可能类似下面的代码。

    field1 some value
    field2 another value

    我们会用RST字段列表的一种扩展形式来编写API文档。我们会将字段名扩展为一个包含多个部分的元素。我们会添加一些关键字作为前缀,例如param或者type。前缀之后是参数名。

    有一些字段前缀可以选择。我们可以选择下面这些前缀中的任何一个:paramparameterargargumentkeykeyword。例如,我们可能会写下面这样的代码。

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

    我们通常用param(或parameter)作为位置参数、key(或keyword)作为关键字参数。我们建议不要在Python代码的文档中使用argargument,因为它们与Python的语法类别不匹配。这些前缀可以被用于为其他语言的shell脚本或API文档。

    这些字段列表的定义会被统一记录到一个缩进的块中。Sphinx工具也会比较文档中和函数参数列表中的名称,确保它们是匹配的。

    我们也可以用type作为前缀定义参数类型。

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

    由于Python很灵活,因此这种定义的细节可能不是必需的。在许多情况下,参数值只需要是简单的数字:param somearg:,可以包含一些通用类型的信息作为描述。我们在前面的例子中已经展示了这种样式:Numeric rank of the card

    对于有返回值的函数,我们应该描述结果,可以用returns或者return字段标签汇总返回值。我们也可以正式地用rtype指出返回值的类型。我们可能会编写下面这样的代码。

    :returns: soft total for this card
    :rtype: integer

    另外,我们应该也包含关于这个函数特有的异常信息。有4个别名可以用于这个字段:raisesraiseexceptexception。我们可能会编写下面这样的代码。

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

    我们也可以描述类的属性。对于这种情况,可以用varivar或者cvar。可能会编写类似下面这样的代码。

    :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输出而言,没有任何不同。

    这些字段列表用于为类、类方法和独立的函数准备文档字符串。我们会在后面的部分中介绍这些字段的用法。

    18.5.2 编写类和方法函数的文档字符串

    类通常会包含许多元素、属性和方法函数。一个有状态的类的API可能相对会复杂一些。创建对象、改变状态以及可能在对象的生命快结束时还需要进行垃圾回收。我们可能想在类的文档字符串或方法函数的文档字符串中对这些状态改变中的一些(或全部)进行描述。

    我们会用字段列表技术在全局的类文档字符串中记录类变量。这通常会专注于使用:ivar variable::cvar variable::var variable:字段列表元素。

    每个独立的方法函数还会用字段列表定义参数并返回每个方法函数抛出的异常和返回值。下面是我们开始编写带有描述类和方法函数文档字符串类的一种方法。

    class Card:
      """Definition of a numeric rank playing card.
      Subclasses will define "FaceCard" and "AceCard".
      :ivar rank: Rank
      :ivar suit: Suit
      :ivar hard: Hard point total for a card
      :ivar soft: Soft total; same as hard for all cards except Aces.
      """
      def init( self, rank, suit, hard, soft=None ):
        """Define the values for this card.

        :param rank: Numeric rank in the range 1-13.
        :param suit: Suit object (often a character from '♣♥◇♠')
        :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

    当我们在文档字符串中包含这种RST标记时,类似Sphinx这样的工具就可以格式化出非常美观的HTML输出。我们已经为你提供了实例变量类级别的文档和其中一个方法函数参数的方法级别文档。

    当与help()一起使用时,RST是可见的。这样的做法不是非常让人反感,因为它在语义上是有意义的并且不会产生困惑。这意味着我们可能需要在help()文本和Sphinx文档中做出的一些平衡。

    18.5.3 编写函数文档字符串

    一个函数文档字符串可以用字段列表格式化来定义参数、返回值和抛出的异常。下面是一个包含了文档字符串的示例函数。

    def card( rank, suit ):
      """Create a "Card" instance from rank and suit.
      :param rank: Numeric rank in the range 1-13.
      :param suit: Suit object (often a character from '♣♥◇♠')
      :returns: Card instance
      :raises TypeError: rank out of range.

      >>> import p3_c18
      >>> p3_c18.card( 3, '♥' )
      3♥
      """
      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( 'rank out of range' )

    这个函数的文档字符串包含参数定义、返回值和抛出的异常。有4个单独的字段列表元素被用于标准化API。我们也已经包含了一个doctest序列。当我们在Sphinx中为这个模块编写文档时,我们会获得一个非常美观的HTML输出。另外,我们可以用doctest工具来确认函数与简单的测试用例匹配。