代码和文档风格指南 - 缺失的部分#

这是一份 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”的复杂性,可以使用普通类或数据类。 使用数据类时,不要使用 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 参数。

例如,如果输入包含 nan 并且 nan_policy"raise",则函数 scipy.stats.zmap 应该引发 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 而通过。