NEP 23 — 向后兼容性和弃用政策#

作者

拉尔夫·戈默斯 <拉尔夫.戈默斯@gmail ​ com >

地位

积极的

类型

过程

创建

2018-07-14

解决

https://mail.python.org/pipermail/numpy-discussion/2021-January/081423.html

抽象的

在此 NEP 中,我们描述了 NumPy 的向后兼容性方法、其弃用和删除政策,以及考虑破坏向后兼容性的个别情况的权衡和决策过程。

动机和范围#

NumPy 拥有非常庞大的用户群。这些用户依赖 NumPy 的稳定性以及他们编写的使用 NumPy 功能的代码来继续工作。 NumPy 也得到了积极的维护和改进——有时改进需要通过破坏向后兼容性,或者通过破坏向后兼容性变得更容易。最后,现有用户的稳定性与避免错误或为新用户提供更好的用户体验之间存在权衡。这些相互竞争的需求常常会引起长时间的争论,并延迟接受或拒绝捐款。本 NEP 试图通过提供政策以及示例和理由来说明何时打破向后兼容性是一个好主意或不是一个好主意,从而解决这个问题。

此外,此 NEP 可以作为用户的文档,介绍 NumPy 项目如何处理向后兼容性以及他们可以预期进行更改的速度。

本 NEP 的范围包括:

  • NumPy 向后兼容性方法的原则。

  • 如何弃用功能,以及何时删除已弃用的功能。

  • 弃用和删除的决策过程。

  • 如何确保用户充分了解任何更改。

超出范围的是:

  • 针对特定功能的弃用做出具体决定。

  • NumPy 的版本控制方案。

一般原则

在考虑向后不兼容的拟议更改时,NumPy 开发人员在做出决定时使用的主要原则是:

  1. 变革应该给用户带来更多好处,而不是伤害用户。

  2. NumPy 被广泛使用,因此默认情况下应假定重大更改是有害的。

  3. 决策应基于它们如何影响用户和下游软件包,并应尽可能基于使用数据。这种用法是否与文档或最佳实践相矛盾并不重要。

  4. 错误结果的可能性比错误甚至崩溃更严重。

在评估拟议更改的成本时,请记住,大多数用户不会阅读邮件列表,不会注意到弃用警告,有时会等待一两年以上才能从旧版本升级。而且 NumPy 拥有数百万用户,因此“没有人会做或使用它”可能是不正确的。

拟议变更的好处包括改进功能、可用性和性能,以及降低维护成本和改进未来的可扩展性。

对明显错误的修复不受此向后兼容性政策的约束。然而,如果对用户造成严重影响,甚至可能不得不推迟一个或多个版本的错误修复。例如,如果下游库不再构建或给出不正确的结果。

实施弃用和删除#

在最终删除功能的所有情况下,弃用警告都是必要的。如果无意删除功能,则不应弃用它。应使用文档中的“请不要将其用于新代码”或其他类型的警告,并且可以组织文档,以便更突出地显示首选替代方案。

弃用:

  • 应包含已弃用该功能的发行版的版本号。

  • 应包含有关已弃用功能的替代方案的信息,或者如果没有明确的替代方案,则应包含弃用的原因。请注意,如果需要,发行说明可以包含更长的消息。

  • DeprecationWarning默认使用,以及VisibleDeprecation 在已被弃用或由于某种原因需要额外注意后需要再次注意的更改。

  • 应在首次弃用的版本的发行说明中列出。

  • 不得在微(错误修复)版本中引入。

  • 应设置 a stacklevel,以便警告看起来来自正确的位置。

  • 应在功能文档中提及。为此可以使用指令。 .. deprecated::

良好弃用警告的示例(另请注意警告上方注释的标准形式,在 grep 时有所帮助):

# NumPy 1.15.0, 2018-09-02
warnings.warn('np.asscalar(a) is deprecated since NumPy 1.16.0, use '
              'a.item() instead', DeprecationWarning, stacklevel=3)

# NumPy 1.15.0, 2018-02-10
warnings.warn("Importing from numpy.testing.utils is deprecated "
              "since 1.15.0, import from numpy.testing instead.",
              DeprecationWarning, stacklevel=2)

# NumPy 1.14.0, 2017-07-14
warnings.warn(
    "Reading unicode strings without specifying the encoding "
    "argument is deprecated since NumPy 1.14.0. Set the encoding, "
    "use None for the system default.",
    np.VisibleDeprecationWarning, stacklevel=2)
/* DEPRECATED 2020-05-13, NumPy 1.20 */
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
        matrix_deprecation_msg, ufunc->name, "first") < 0) {
    return NULL;
}

删除已弃用的功能:

  • 假设当前的发布周期为 6 个月,则应在至少 2 次发布之后完成;如果情况发生变化,则弃用和删除之间应至少间隔 1 年。

  • 应在发生删除的版本的发行说明中列出。

  • 可以在任何次要版本(但不能修复错误)中完成。

对于不是“弃用和删除”但代码将开始表现不同的向后不兼容的更改,FutureWarning应该使用 a 。发行说明、提及版本号和使用stacklevel应以与弃用警告相同的方式进行。 行为更改后,应在文档中使用指令来指示行为何时更改:.. versionchanged::

def argsort(self, axis=np._NoValue, ...):
    """
    Parameters
    ----------
    axis : int, optional
        Axis along which to sort. If None, the default, the flattened array
        is used.

        ..  versionchanged:: 1.13.0
            Previously, the default was documented to be -1, but that was
            in error. At some future date, the default will change to -1, as
            originally intended.
            Until then, the axis should be given explicitly when
            ``arr.ndim > 1``, to avoid a FutureWarning.
    """
    ...
    warnings.warn(
        "In the future the default for argsort will be axis=-1, not the "
        "current None, to match its documentation and np.argsort. "
        "Explicitly pass -1 or None to silence this warning.",
        MaskedArrayFutureWarning, stacklevel=3)

决策

在需要应用该策略的具体情况下,根据NumPy 治理模型做出决策。

所有弃用的内容都必须在邮件列表中提出,以便让每个对 NumPy 开发感兴趣的人都有机会发表评论。删除已弃用的功能不需要在邮件列表上进行讨论。

具有更严格弃用政策的功能#

  • numpy.random有自己的向后兼容性政策,除此 NEP 中的要求外还有其他要求,请参阅 NEP 19

  • .npy和文件的文件格式.npz严格受版本控制,与 NumPy 版本无关;即使引入更新的格式版本,现有格式版本也必须保持向后兼容。

示例案例#

我们现在讨论 NumPy 历史上的一些具体示例,以说明典型问题和权衡。

改变函数的行为

np.histogram这可能是最臭名昭著的例子。首先,引入了一个新的关键字new=False,然后在一个版本之后将其切换为 None,最后再次将其删除。此外,它有一个normed关键字,其行为可能被认为是次优或损坏的(取决于人们对统计数据的看法)。density引入了一个新的关键字来替代它;仅在 v.1.15.0 中normed开始给予 。DeprecationWarning的演变histogram

def histogram(a, bins=10, range=None, normed=False):  # v1.0.0

def histogram(a, bins=10, range=None, normed=False, weights=None, new=False):  #v1.1.0

def histogram(a, bins=10, range=None, normed=False, weights=None, new=None):  #v1.2.0

def histogram(a, bins=10, range=None, normed=False, weights=None):  #v1.5.0

def histogram(a, bins=10, range=None, normed=False, weights=None, density=None):  #v1.6.0

def histogram(a, bins=10, range=None, normed=None, weights=None, density=None):  #v1.15.0
    # v1.15.0 was the first release where `normed` started emitting
    # DeprecationWarnings

new关键字从一开始就被计划为临时的。这样的计划迫使用户多次更改代码,这几乎从来都不是正确的做法。相反,更好的方法是弃用histogram并引入新函数hist来代替。

禁止使用浮动索引

使用浮点数对数组进行索引要求的内容不明确,并且可能是用户代码中存在错误的迹象。经过一番讨论,人们认为弃用浮动索引是个好主意。这是在 v1.8.0 版本中首次尝试的,但是在预发布测试中,很明显这会破坏许多依赖 NumPy 的库。因此,它在发布之前被恢复,以便让这些库有时间首先修复其代码。它最终在 v1.11.0 中引入,并在 v1.12.0 中变成了硬错误。

这一变化是破坏性的,但它确实捕获了 SciPy 和 scikit-learn 等中的真正错误。总体而言,更改是值得的,首先将其引入主分支以允许测试,然后在发布之前再次删除它,是一个有用的策略。

类似的弃用看起来也像是清理/改进的好例子:

  • 删除已弃用的布尔索引(2016 年,请参阅gh-8312

  • 弃用对空数组的真值测试(2017 年,请参阅gh-9718

取消财务职能

财务函数(例如np.pmt)具有简短的非描述性名称,存在于主 NumPy 命名空间中,并且并不真正适合 NumPy 的范围。它们是在 2008 年 在邮件列表上进行讨论后添加的 ,讨论中存在意见分歧(但大多数人赞成)。财务功能并没有造成太多开销,但是每年仍然会出现多个问题和 PR,这会花费维护人员的时间来处理。它们使numpy命名空间变得混乱。关于删除它们的讨论曾在 2013 年(gh-2880,被拒绝)和 2019 年(NEP 32 - 从 NumPy 中删除财务功能,没有重大投诉的情况下接受)进行过讨论。

鉴于它们显然超出了 NumPy 的范围,因此将它们移至单独的numpy-financial包并在弃用期后将其从 NumPy 中删除是有意义的。这也为用户提供了一种通过执行pip install numpy-financial来更新代码的简单方法。

备择方案

更加积极地进行弃用。

更加激进的目标是让 NumPy 能够更快地前进。这将避免其他人发明自己的解决方案(通常在多个地方),并且对没有遗留代码库的用户有利。我们拒绝这种替代方案,因为 NumPy 在科学 Python 生态系统中的地位 - 需要相当保守,以免将下游库和最终用户的额外维护增加到不可接受的水平。

讨论

参考文献和脚注#