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 中开始发出 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 生态系统中的位置 - 必须保持相当保守,以避免增加下游库和最终用户无法接受的额外维护负担。

讨论#

参考资料和脚注#