程式碼與文件風格指南 - 遺漏的細節#

這份文件收集了 SciPy 的程式碼與文件撰寫指南,這些指南並未明確地在現有的指南和標準中說明,包含:

其中有些看似瑣碎,可能不值得討論,但在許多情況下,這些問題已在 SciPy 或 NumPy 儲存庫的 pull request 審查中出現。如果一個風格問題重要到審查者會要求在合併前進行修改,那麼它就值得記錄下來——至少對於那些可以透過簡單規則解決的問題而言。

程式碼風格與指南#

請注意,儘管 docstring 是 Unicode,但通常應由 ASCII 字元組成。以下來自 tools/check_unicode.py 檔案的程式碼區塊告訴 linter 哪些額外的字元是允許的

18latin1_letters = set(chr(cp) for cp in range(192, 256))
19greek_letters = set('αβγδεζηθικλμνξoπρστυϕχψω' + 'ΓΔΘΛΞΠΣϒΦΨΩ')
20box_drawing_chars = set(chr(cp) for cp in range(0x2500, 0x2580))
21extra_symbols = set('®ő∫≠≥≤±∞²³·→√')
22allowed = latin1_letters | greek_letters | box_drawing_chars | extra_symbols

必要的關鍵字名稱#

對於參數多於幾個的新函式或方法,在最初幾個「顯而易見」的參數之後的所有參數,都應要求在使用時使用關鍵字。這透過在簽名中的適當位置包含 * 來實現。

例如,一個作用於單一陣列但具有多個可選參數(例如 methodflagrtolatol)的函式 foo 將定義為

def foo(x, *, method='basic', flag=False, rtol=1.5e-8, atol=1-12):
    ...

要呼叫 foo,除了 x 以外的所有參數都必須使用明確的關鍵字給定,例如 foo(arr, rtol=1e-12, method='better')

這強制呼叫者給出明確的關鍵字參數(即使不使用 *,大多數使用者可能也會這樣做),而且這表示可以在 * 之後的任何位置向函式添加額外參數;新的參數不必添加到現有參數之後。

傳回物件#

對於傳回兩個或多個概念上不同的元素的新函式或方法,請以不可迭代的物件類型傳回這些元素。特別是,不要傳回 tuplenamedtuple,或由 scipy._lib._bunch.make_tuple_bunch 產生的「bunch」,後者保留用於向現有函式傳回的可迭代物件添加新屬性。相反地,使用現有的傳回類別(例如 OptimizeResult)或新的自訂傳回類別。

這種傳回不可迭代物件的做法,迫使呼叫者更明確地說明他們想要存取的傳回物件的元素,並且使其更容易以向後相容的方式擴展函式或方法。

如果傳回類別很簡單且不是公開的(即無法從公共模組匯入),則可以像這樣記錄:

Returns
-------
res : MyResultObject
    An object with attributes:

    attribute1 : ndarray
        Customized description of attribute 1.
    attribute2 : ndarray
        Customized description of attribute 2.

上面的「MyResultObject」沒有連結到外部文件,因為它足夠簡單,可以直接在其名稱下方完整記錄所有屬性。

有些傳回類別足夠複雜,值得擁有自己的渲染文件。如果傳回類別是公開的,這相當標準,但傳回類別只有在 1) 預期被終端使用者匯入且 2) 已獲得論壇批准時才應公開。對於複雜的私有傳回類別,請參閱 binomtest 如何總結 BinomTestResult 並連結到其文件,並注意 BinomTestResult 無法從 stats 匯入。

根據「MyResultObject」的複雜性,可以使用普通類別或 dataclass。當使用 dataclass 時,不要使用 dataclasses.make_dataclass,而是使用正確的宣告。這允許自動完成列出結果物件的所有屬性,並改進靜態分析。最後,隱藏任何私有屬性

@dataclass
class MyResultObject:
    statistic: np.ndarray
    pvalue: np.ndarray
    confidence_interval: ConfidenceInterval
    _rho: np.ndarray = field(repr=False)

來自 numpy.testing 的測試函式#

在新程式碼中,不要使用 assert_almost_equalassert_approx_equalassert_array_almost_equal。以下是這些函式的 docstring:

It is recommended to use one of `assert_allclose`,
`assert_array_almost_equal_nulp` or `assert_array_max_ulp`
instead of this function for more consistent floating point
comparisons.

有關編寫單元測試的更多資訊,請參閱 NumPy 測試指南

測試預期的例外狀況/警告#

當編寫新的測試,測試函式呼叫是否引發例外狀況或發出警告時,首選的風格是使用 pytest.raises/pytest.warns 作為上下文管理器,並將應該引發例外狀況的程式碼放在上下文管理器定義的程式碼區塊中。match 關鍵字參數會附帶足夠的預期訊息,以將其與同一類別的其他例外狀況/警告區分開來。不要使用 np.testing.assert_raisesnp.testing.assert_warns,因為它們不支援 match 參數。

例如,函式 scipy.stats.zmap 應該在輸入包含 nannan_policy"raise" 時引發 ValueError。對此的測試是

scores = np.array([1, 2, 3])
compare = np.array([-8, -3, 2, 7, 12, np.nan])
with pytest.raises(ValueError, match='input contains nan'):
    stats.zmap(scores, compare, nan_policy='raise')

match 參數確保測試不會因為引發與輸入包含 nan 無關的 ValueError 而通過。