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 生态系统中的地位——相当保守是必需的,以避免增加下游库和最终用户的额外维护成本到不可接受的水平。

讨论#

参考文献和脚注#