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;
}

删除弃用功能

  • 应在至少 2 个版本之后进行,假设当前是每 6 个月发布一次;如果发生变化,则弃用和删除之间应至少间隔 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 中开始给出 `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` 来代替它。

不允许使用浮点数进行索引

使用浮点数索引数组是模糊的,并且可能是用户代码中的 bug 迹象。经过一些讨论,认为弃用使用浮点数进行索引是一个好主意。这最初是在 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` 命名空间变得混乱。

鉴于它们显然超出了 NumPy 的范围,将它们移动到单独的 `numpy-financial` 包中,并在弃用期后从 NumPy 中删除是有意义的。这还为用户提供了一种轻松更新代码的方法,只需执行 `pip install numpy-financial`。

替代方案#

更积极地进行弃用。

更积极的目的是让 NumPy 能够更快地向前发展。这将避免其他人发明自己的解决方案(通常在多个地方),以及为没有遗留代码库的用户带来好处。我们拒绝此替代方案,因为 NumPy 在科学 Python 生态系统中的位置——保持相当保守是必要的,以避免将下游库和最终用户的额外维护增加到不可接受的水平。

讨论#

参考文献和脚注#