2013年1月2日 星期三

[Python] PEP 257:Docstring慣例

 本文翻譯自 Python 官方網站中之 PEP 257 -- Docstring Conventions 一文,該 PEP 說明了Python 程式碼中關於 docstring 格式與內容的規範。

 譯註:所謂 docstring 就是放在程式碼中用以說明某物件功能的字串常數,通常可以被 IDE 或其他軟體工具取出並顯示給開發者查閱使用,雖然功能是用來說明程式碼功能的,但不同於一般塞在程式碼裡一行一行的註解 (comments),docstring 著重的是物件整體的概括說明。

摘要

 這份 PEP 記載了關於 Python docstring 的語義內容與撰寫慣例。

合理性

 這份 PEP 的目的是將 docstring 的 high-level structure 進行標準化:docstrings 應該包含什麼、還有怎麼表達內容(但不涉及任何 docstring 中的標記語法等內容)。 這份 PEP 包含的是慣例,而非強制要求或語法。

「一個普遍的慣例將支持著所有的可維護性、清晰度、一致性以及良好撰寫習慣的基礎。但他絕對不會達成的,就是要求你違背你自己的意思及作法。這就是 Python!」

- Tim Peters,2001-06-16

 如果你不遵守這些慣例,你頂多就是得到一些白眼。但某些軟體(例如 Docstring [4] docutils 處理系統 [1] [2] )會識別這些慣例,所以您遵從這些慣例將使你得到最好的結果。

規格說明

Docstring 是什麼?

 Docstring 是放在模組 (module)、函式 (function)、類別 (class)、或方法 (method) 定義中第一個敘述 (statements) 處的字串常數 (string literal;譯註:即單純的字串內容,如 'abc'''' a string ''')。這樣的 docstring 會成為該物件的 __doc__ 特殊屬性。

 所有的模組都應該要有 docstrings,而且所有模組中可自外部調用的函數和類別也應該要有 docstrings。 所有公共方法(包含建構子 __init__)也都應該有 docstrings。 關於套件的相關說明則可能被記錄在該套件目錄下 __init__.py 檔中的 docstring 裡。

 出現在 Python 程式碼中其他地方的字串常數也有可能被當作文件來使用。但這些字串將無法被 Python 編譯器識別,而且也無法以執行時期物件屬性的方式來存取(也就是不會被指派給 __doc__)。但有兩種額外的 docstrings 是可能可以被特殊工具所取出的:
  1. 字串常數字符串字面量在頂層的一個模塊,類或__init__方法一個簡單的賦值發生後,立即被稱為“屬性的文檔字符串”。
  2. 發生後,立即另一個文檔字符串的字符串文字被稱為“額外的文檔字符串”。
關於屬性與額外 docstrings 的詳細說明,可參閱 "PEP 258:Docutils 設計規格" [2]

 為了保持一致性,請總是使用 """三個雙引號""" 來建立 docstrings。 如果你在你的 docstring 中有使用任何的反斜線,則請使用 r"""代表原始字串的三個雙引號"""。對於Unicode docstrings,則使用 u"""代表 Unicode 字串的三個雙引號"""

 Docstrings 共有兩種形式:單行與多行 docstrings。

單行 docstrings

 單行 docstring 僅應使用於說明內容真的很明顯,並且真的只需要寫成一行時。例如:

def kos_root():
    """ Return the pathname of the KOS root directory. """
    global_kos_root
    if _kos_root: return _kos_root
    ...

注意:

  • 即便字串可以完全放在一行中,也使用三個雙引號。這可以讓 docstring 之後能容易地擴展。
  • 終止引號與起始引號是放在同一行的。這能讓單行 docstring 看起來更美觀。
  • 不管在 docstring 之前或之後都沒有空白行。
  • Docstring 的內容應該是以一段時間作為結束的短句子。它應該以類似命令的方式來規定函式或方法的效果(像是:"Do this"、"Return that"),而不是一段描述。例如,不要寫成 "Returns the pathname..."。
  • 單行 docstring 不應該只是單單重複方法參數與回傳值的 "signature"(那些資訊可以通過 introspection 得到)。例如,不要像下面這樣:
  • def function(a, b):
        """ function(a, b) -> list """
  • 這種 docstring 只適用於 C函數中(例如在製作 plug-ins 時),不可能使用 introspection 時。然而,由於在一般 Python 程式中即便使用 introspection 依舊無法得知函式或方法回傳值的型態,所以回傳值的意義與資料型態依舊應該在 docstring 中說明。因此我們偏好的 docstring 形式是這樣的:
def function (a, b):
    """ Do X and return a list. """
(當然,X 應該要是一個有用的描述!)

多行 docstrings

 多行 docstring 的第一個部份是一句像是單行 docstring 般用以說明整個物件功能摘要的語句,後面接著一行空白行,然後是一段更詳細的描述。為了讓摘要行(譯註:第一行)可以被自動索引工具所使用,請務必將它只寫成一行,並與該 docstring 的其他部份分開。摘要行,可能與起始引號位在同一行,或位於起始引號的下一行。整個 docstring 則應該與第一行的引號有著相同的縮排(見下面的例子)。

 所有在類別中的 docstrings(包含單行與多行docstring)都應該在首行之前與尾行之後各插入一個空行。一般來說,類別的方法彼此間會各以一個空白行來分隔,而 docstring 又需要以一個空白行來與第一個方法做區分。因此,為了對稱的緣故,我們也會在類別的開頭處與 docstring 間加入一個空白行。函式或方法的 docstring 一般不會有這樣的要求,除非該函式或方法的主體被寫成了數個以空白行分離的段落-在這種情況下,我們也會把 docstring 當作是其中一個段落,並在它前面加上一個空白行。

 Script(一支獨立的 Python 程式)的 docstring 應該要作為其「使用方法」,當該程式被以不正確的或缺漏的參數叫用時顯示在使用者面前(或透過 "-h" 選項,當作 "help" 來使用)。這種 docstring 應該要記錄該程式的功能和命令列語法、環境變數、與相關檔案等。使用方法訊息
可能相當的詳盡(長到能塞滿好幾個螢幕),而且資訊應該要充足到對於新的使用者來說能正確的使用,而且對舊使用者來說也要能成為一份關於選項與參數的詳盡參考。

 模組的 docstring 則應該以單行摘要的方式,針對所能提供給外界使用的類別、例外、與函式(以及任何其他物件)列舉並說明(與物件本身的 docstring 相比,這些摘要行給的資訊一般來說較少。)套件的 docstring(亦即套件中 __ init__.py 模組的 docstring)也應該詳列並說明可供外界使用的各模組與子套件。

 函式或方法的 docstring 應該總​​結自己的行為並記錄其參數、回傳值、副作用、可能發生的例外、以及被調用時的限制等(如果可以的話,請全部記載)。選擇性參數也應該標明。關鍵字參數是否為該調用界面的一部分也應該被記載下來。

 類別的 docstring 應該總​​結自己的行為,並列出其公共方法及實體變數。如果該類別的用途就是要拿來被繼承的,而且對於子類別來說有額外的界面可使用時,該界面應該在 docstring 中另外說明。類別建構子行為應該被記錄其 __init__ 方法的 docstring 中。其他個別的方法行為則應該記錄在自己的 docstring 裡。

 如果一個類別繼承自另一個類別,而且其行為主要來自那個類別的話,那個 docstring 則應該提到這一點,並說明兩類別行為的不同之處。請使用動詞「覆載」(override) 來說明一個子類別方法已取代了父類別版本的同名稱方法,而且不調用父類別版本的時候;或者使用「擴增」(extend) 說明子類別方法會調用父類別版本方法的情況。

 不要使用 Emacs 在執行時期時一提到函式或方法的參數就會使用大寫的那種慣例。Python 是 case-sensitive 的,而且參數名稱是可以當作關鍵字參數使用的。所以 docstring 中應該記錄正確的參數名稱。最好對於每個參數都以獨立的一行列出並說明。例如:

def complex(real= 0.0, imag= 0.0):
    """ 建立一個複數

    Keyword arguments:
    real - 實數部份 (default 0.0)
    imag  - 虛數部份 (default 0.0)

    """
    if imag == 0.0 and real == 0.0: return complex_zero
    ...

 BDFL [3] 建議在多行 docstring 的最後一個段落與終止引號間插入一個空白行,並讓終止引號自己成為一行。如此一來,Emacs 的 fill-paragraph 命令就可以使用了。

處理 docstring 縮排 

 Docstring 處理工具一般來說會將 doctring 中自第二行開始的所有內容裡的縮排進行處理,並把各行的縮排空白數刪除至各行的最小值。Docstring 中第一行(直到第一個 new line 出現為止)的縮排都是無關緊要的並且會被刪除掉。其餘各行的相對縮排則會被保留下來。Docstring 開頭與結束的空白行則應該被刪除掉。

 由於程式碼比文字更精確,這裡是上述演算法的一個實作方式:

def trim(docstring)
    if not docstring:
        return ''
    # 將tab轉為space(遵循正常的Python規範)
    # 並將其切成字串list:
    lines = docstring.expandtabs().splitlines()
    # 決定最小縮排數(第一行不計):
    indent = sys.maxint
    for line in lines[1:]:
        stripped = line.lstrip()
        if stripped:
            indent = min(indent, len(line) - len(stripped))
    # 刪掉縮排(第一行應額外處理):
    trimmed = [lines[0].strip()]
    if indent < sys.maxint:
        for line in lines[1:]:
            trimmed.append(line[indent:].rstrip())
    # 去掉頭尾的空白行
    while trimmed and not trimmed[-1]:
        trimmed.pop()

    while trimmed and not trimmed[0]:
        trimmed.pop(0)
    # 回傳處理完畢的單一字串物件

    return '\n'.join(trimmed)

 這個例子共有兩個 new line 字元與三行文字,頭尾兩航都是空白的。

def foo():
    """
    This is the second line of the docstring.
    """

將上述程式碼套用至此例中:

>>> print repr(foo.__doc__)
'\n    This is the second line of the docstring.\n    '
>>> foo.__doc__.splitlines()
['', '    This is the second line of the docstring.', '    ']
>>> trim(foo.__doc__)
'This is the second line of the docstring.'

 在經過處理後,這些以下兩個 docstring 是等價的:


def foo():
    """A multi-line
    docstring.
    """

def bar():
    """
    A multi-line
    docstring.
    """


參考和註腳

[1] PEP 256, Docstring Processing System Framework, Goodger (http://www.python.org/dev/peps/pep-0256/)
[2] (1, 2) PEP 258, Docutils Design Specification, Goodger (http://www.python.org/dev/peps/pep-0258/)
[3] Guido van Rossum, Python's creator and Benevolent Dictator For Life.
[4] http://docutils.sourceforge.net/
[5] http://www.python.org/doc/essays/styleguide.html
[6] http://www.python.org/sigs/doc-sig/


沒有留言:

張貼留言