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

作者:

Ralf Gommers <ralf.gommers@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

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

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

  • 应设置 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。版本说明、提及版本号和使用 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 来替换它;normed 仅在 v.1.15.0 中开始发出 DeprecationWarninghistogram 的演变

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 生态系统中的地位——为了不使下游库和最终用户的额外维护增加到不可接受的水平,必须保持相当保守。

讨论#

参考文献和脚注#