NEP 52 — NumPy 2.0 的 Python API 清理#

作者

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

作者

斯特凡·范德沃尔特< stefanv @伯克利教育>

作者

内森·戈德鲍姆< ngoldbaum @ quansight com >

作者

Mateusz Sokół < msokol @quansight。com >

地位

公认

类型

标准轨道

创建

2023-03-28

解决

https://mail.python.org/archives/list/numpy-discussion@python.org/thread/QLMPFTWA67DXE3JCUQT2RIRLQ44INS4F/

抽象的

我们建议在 NumPy 2.0 版本中清理 NumPy 的 Python API。这包括在公共和私有之间进行更明确的划分,并通过删除具有更好替代方案的别名和函数来减小主命名空间的大小。此外,每个函数只能从一个地方访问,因此所有重复项也需要删除。

动机和范围#

NumPy 拥有一个庞大的 API 接口,并且经过多年的有机发展:

>>> objects_in_api = [s for s in dir(np) if not s.startswith('_')]
>>> len(objects_in_api)
562
>>> modules = [s for s in objects_in_api if inspect.ismodule(eval(f'np.{s}'))]
>>> modules
['char', 'compat', 'ctypeslib', 'emath', 'fft', 'lib', 'linalg', 'ma', 'math', 'polynomial', 'random', 'rec', 'testing', 'version']
>>> len(modules)
14

上述内容甚至不包括公开但已隐藏的项目__dir__。一个特别有问题的例子是np.core,它在技术上是私有的,但在实践中被大量使用。有关公共、私有或介于两者之间的内容的完整概述,请参阅 numpy/numpy

API 的规模及其边界的缺乏定义会带来巨大的成本:

  • 用户发现很难区分名称相似的函数之间的歧义。

    在 IPython、笔记本或 IDE 中寻找具有制表符补全功能的函数是一项挑战。例如,键入np.<TAB>并查看提供的前六个项目:两个 ufunc ( abs, add)、一个别名 ( absolute) 和三个不适合最终用户的函数 ( add_docstring, add_newdoc, add_newdoc_ufunc)。因此,NumPy 的学习曲线比应有的要陡峭。

  • 模仿 NumPy API 的库面临着巨大的实施障碍。

    对于 NumPy API 兼容数组库(Dask、CuPy、JAX、PyTorch、TensorFlow、cuNumeric 等)和编译器/转译器(Numba、Pythran、Cython 等)的维护者来说,命名空间中的每个对象都有实现成本。实际上,没有其他库能够完全支持整个 NumPy API,部分原因是在面对大量别名和遗留对象时很难知道要包含哪些内容。

  • 教授 NumPy 比实际需要的更加复杂。

    同样,较大的 API 也会让学习者感到困惑,他们不仅必须找到 函数,还必须选择要使用的函数

  • 开发人员对于扩大 API 接口犹豫不决。

    即使需要进行更改,这种情况也会发生,因为他们意识到上述问题。

本 NEP 的范围包括:

  • 弃用或删除对于 NumPy 来说太小众、设计不佳、被更好的替代方案、不必要的别名或其他需要删除的候选功能所取代的功能。

  • 使用下划线明确区分公共和私有 NumPy API。

  • 重构 NumPy 命名空间,使其更易于理解和导航。

超出本 NEP 范围的包括:

  • 引入新功能或性能增强。

使用和影响#

此 API 重构的一个关键原则是确保当代码适应了更改并且与 2.0 兼容时,该代码也可以NumPy 一起使用1.2x.x。由于不必携带打开 NumPy 主版本号的重复代码,因此可以减轻用户和下游库维护人员的负担。

向后兼容性#

如上所述,虽然新的(或清理后的 NumPy 2.0)API 应该向后兼容,但不能保证从 1.25.X 到 2.0 的向前兼容性。代码必须更新,以考虑已弃用、移动或删除的函数/类,以及更严格执行的私有 API。

为了更容易地采用本新经济政策中的变更,我们将:

  1. 提供列出每个 API 更改及其替换的转换指南。

  2. 使用有意义的值明确标记所有过期属性AttributeError ,指出新位置或推荐替代方案。

  3. 尽可能提供一个脚本来自动执行迁移。这将类似于tools/replace_old_macros.sed(针对之前的 C API 命名方案更改调整代码)。这将sed基于(或等效)而不是尝试 AST 分析,因此它不会涵盖所有内容。

详细说明

清理主命名空间#

我们期望将主命名空间减少大量条目,大约 100 个。以下是一组代表性示例:

  • np.inf它们之间有np.nan8 个别名,其中大部分可以删除。

  • gh-12385中列出的 随机和未记录的函数(例如, byte_boundsdispsafe_eval、 )的集合 可以被弃用和删除。who

  • 所有*sctype函数都可以弃用和删除(请参阅 gh-17325gh-12334以及maximum_sctype相关函数的其他问题)。

  • np.compatPython 2 到 3 过渡期间使用的命名空间将被删除。

  • 范围狭窄、公共用例很少的功能将被删除。这些必须通过问题分类手动识别。

np.exceptions为警告/异常 ( ) 和数据类型相关功能 ( )引入了新的命名空间np.dtypes。 NumPy 2.0 是从主命名空间填充这些子模块的好机会。

广泛使用但有首选替代方案的功能可能会被弃用(弃用消息指出要使用什么),或者通过不将其包含在__dir__.在隐藏的情况下,可以使用目录来在文档中标记此类功能。.. legacy::

将添加测试以确保所有命名空间的未来增长有限;即,每个新条目都需要明确添加到允许列表中。

清理子模块结构#

我们将清理 NumPy 子模块结构,以便更容易导航。当之前讨论过这个问题时(请参阅 MAINT:隐藏 np.lib 的内部结构以仅显示子模块),已经对此达成了粗略的共识 - 然而在次要版本中很难实现。

我们将坚持的一个基本原则是“一个功能,一个地点”。在多个命名空间中公开的函数(例如,numpy和中存在许多函数numpy.lib)需要找到一个家。

我们将沿着主模块和子模块命名空间重新组织 API 参考指南,并且仅在主命名空间内使用当前的功能分组细分。还可以通过“主流”和特殊用途的命名空间:

# Regular/recommended user-facing namespaces for general use. Present these
# as the primary set of namespaces to the users.
numpy
numpy.exceptions
numpy.fft
numpy.linalg
numpy.polynomial
numpy.random
numpy.testing
numpy.typing

# Special-purpose namespaces. Keep these, but document them in a separate
# grouping in the reference guide and explain their purpose.
numpy.array_api
numpy.ctypeslib
numpy.emath
numpy.f2py  # only a couple of public functions, like `compile` and `get_include`
numpy.lib.stride_tricks
numpy.lib.npyio
numpy.rec
numpy.dtypes
numpy.array_utils

# Legacy (prefer not to use, there are better alternatives and/or this code
# is deprecated or isn't reliable). This will be a third grouping in the
# reference guide; it's still there, but de-emphasized and the problems
# with it or better alternatives are explained in the docs.
numpy.char
numpy.distutils
numpy.ma
numpy.matlib

# To remove
numpy.compat
numpy.core  # rename to _core
numpy.doc
numpy.math
numpy.version  # rename to _version
numpy.matrixlib

# To clean out or somehow deal with: everything in `numpy.lib`

笔记

TBD:我们会保留np.lib还是不保留?它只有几个独特的函数/对象,例如Arrayterator(要删除的候选者)、NumPyVersionstride_tricksmixinsformat子模块。 numpy.lib本身并不是一个连贯的命名空间,甚至没有参考指南页面。

我们将使所有子模块延迟可用,以便用户不必键入 但可以使用,同时不会对 的开销产生负面影响。这对于教学 scikit-image 和 SciPy 非常有帮助,并且它解决了 Spyder 用户的潜在问题,因为 Spyder 已经使所有子模块可用 - 因此使用上述导入模式的代码可以在 Spyder 中运行,但不能在 Spyder 之外运行。import numpy.xxximport numpy as np; np.xxx.*import numpy

减少选择数据类型的方式数量#

许多 dtype 类、实例、别名以及选择它们的方法是 NumPy API 中较大的可用性问题之一。例如:

>>> # np.intp is different, but compares equal too
>>> np.int64 == np.int_ == np.dtype('i8') == np.sctypeDict['i8']
True
>>> np.float64 == np.double == np.float_ == np.dtype('f8') == np.sctypeDict['f8']
True
### Really?
>>> np.clongdouble == np.clongfloat == np.longcomplex == np.complex256
True

这些别名可以go:/devdocs/reference/arrays.scalars.html#other-aliases

所有单字符类型代码字符串和相关例程都mintypecode 将被标记为遗留。

讨论:

  • 所有与 dtype 相关的类移至np.dtypes?

  • 比较/选择 dtypes 的规范方法:(np.isdtype新的,外部引用数组 API NEP),留给np.issubdtypenumpy 的 dtype 类层次结构的更小众用途,并隐藏大多数其他内容。

  • 可能删除float96/ float128?它们是可能不存在的别名,而且很容易搬起石头砸自己的脚。

清理numpy.ndarray#上的利基方法

ndarray对象有很多属性和方法,其中一些属性和方法太小众,不那么突出,只会分散普通用户的注意力。例如:

  • .itemset(已经气馁了)

  • .newbyteorder(太小众了)

  • .ptp(利基,使用np.ptp函数代替)

考虑并拒绝 API 更改#

对于某些功能和子模块,事实证明,删除它们会造成太多干扰,或者需要与实际收益不成比例的工作量。我们对此类项目得出以下结论:

  • 删除工作日功能:np.busday_countnp.busday_offsetnp.busdaycalendar

  • 删除np.nan*函数并向nan_mode相关基本函数引入新参数。

  • 在子模块中隐藏直方图函数np.histograms

  • 隐藏c_,r_s_在子模块中np.lib.index_tricks

  • 看似小众但存在于 Array API 中的函数(例如np.can_cast)。

  • 从物体上移除.repeat并移除。.ctypesndarray

执行

该实现已分为许多不同的 PR,每个 PR 都涉及一个 API 或一组相关的 API。以下是最具影响力的 PR 示例:

2.0版本中完成的清理工作的完整列表可以通过搜索专用标签找到:

一些 PR 已合并并随1.25.0版本一起发布。例如,弃用非首选别名:

隐藏或删除意外公开的对象,甚至根本不公开 NumPy 对象:

创建新的命名空间,以便更轻松地导航模块结构:

备择方案

讨论

参考文献和脚注#