NEP 15 — 合并 multiarray 和 umath#

作者:

Nathaniel J. Smith <njs@pobox.com>

状态:

最终

类型:

标准跟踪

创建:

2018-02-22

决议:

https://mail.python.org/pipermail/numpy-discussion/2018-June/078345.html

摘要#

让我们将 numpy.core.multiarraynumpy.core.umath 合并到单个扩展模块中,并弃用 np.set_numeric_ops

背景#

目前,numpy 的核心 C 代码分为两个独立的扩展模块。

numpy.core.multiarraynumpy/core/src/multiarray/*.c 构建,包含核心数组功能(特别是 ndarray 对象)。

numpy.core.umathnumpy/core/src/umath/*.c 构建,包含 ufunc 机制。

这两个模块分别通过 import_multiarray()import_umath() 公开了各自的 C API。其理念是它们应该是独立的模块,multiarray 作为底层,umath 建立在其之上。实际上,这已被证明是有问题的。

首先,分层并不完美:当你编写 ndarray + ndarray 时,这会调用 ndarray.__add__,然后调用 ufunc np.add。这意味着 ndarray 需要了解 ufunc——因此,我们不是干净的分层,而是存在循环依赖。为了解决这个问题,multiarray 导出一个名为 set_numeric_ops 的相当可怕的函数。每次你 import numpy 时的引导过程是:

  1. multiarray 及其 ndarray 对象已加载,但 ndarray 的算术运算已损坏。

  2. umath 已加载。

  3. 使用 set_numeric_opsumath 中的对象来修补所有方法,例如 ndarray.__add__

此外,set_numeric_ops 作为公共 API np.set_numeric_ops 公开。

此外,即使这种分层有效,它最终也会扭曲我们公共 ABI 的形状。近年来,向 multiarray 的“公共”ABI 添加新函数的最常见原因不是它们真的需要是公共的,也不是我们期望其他项目使用它们,而是我们只需要从 umath 调用它们。这非常不幸,因为它使我们的公共 ABI 不必要地变大,而且由于我们永远无法从中删除内容,因此会造成持续的维护负担。C 的工作方式是,你可以拥有对同一扩展模块内的所有内容可见的内部 API,或者你可以拥有所有人都可以使用公共 API;你不能(容易地)拥有对 numpy 内的多个扩展模块可见,但对外部用户不可见的 API。

我们也越来越多地将实用程序代码放入 numpy/core/src/private/ 中,该目录现在包含许多 #include 了两次的文件,一次到 multiarray,一次到 umath 中。这非常糟糕,纯粹是为了解决这些是独立的 C 扩展程序的问题。npymath 库也包含在两个扩展模块中。

提议的更改#

此 NEP 提出三个更改:

  1. 我们应该开始将 numpy/core/src/multiarray/*.cnumpy/core/src/umath/*.c 构建到单个扩展模块中。

  2. 我们应该使用一些新的私有 API 来设置 ndarray.__add__ 及其相关方法,而不是 set_numeric_ops

  3. 我们应该弃用并最终删除 np.set_numeric_ops

未提出的更改#

我们不一定建议在我们的源代码组织方面抛弃 multiarray/ 和 umath/ 之间的区别:内部组织很有用!我们只是想将它们一起构建到单个扩展模块中。当然,这为潜在的未来重构打开了大门,然后我们可以根据它们出现的优点来评估它们。

它也没有提出要破坏公共 C ABI。我们应该继续提供import_multiarray()import_umath()函数——只是现在这两个ABI最终都将从同一个C库加载。由于import_multiarray()import_umath()的编写方式,我们仍然需要名为numpy.core.multiarraynumpy.core.umath的模块,并且它们需要继续导出_ARRAY_API_UFUNC_API对象——但是我们可以使这两个模块中的一个或两个成为微小的shim,它们只是简单地从实际定义它们的位置重新导出魔术API对象。(有关这些导入的工作方式的详细信息,请参阅numpy/core/code_generators/generate_{numpy,ufunc}_api.py)。

向后兼容性#

唯一的不兼容性中断是弃用np.set_numeric_ops

被拒绝的替代方案#

保留set_numeric_ops用于猴子补丁#

在讨论这个NEP时,提出了set_numeric_ops的另一个用例:如果你有一个优化的向量数学库(例如英特尔的MKL VML、Sleef或Yeppp),那么set_numeric_ops可以用来对numpy进行猴子补丁,以使用这些操作而不是numpy的内置向量操作。但是,即使我们承认这是一个好主意,使用set_numeric_ops实际上并不是最好的方法。所有set_numeric_ops允许你做的是接管ndarray上的Python语法运算符(+,*等);它不允许你影响通过其他API调用的操作(例如np.add),或者没有内置语法的操作(例如np.exp)。此外,你必须重新实现整个ufunc机制,而不仅仅是核心循环。另一方面,PyUFunc_ReplaceLoopBySignature API(于2006年添加)允许替换任意ufunc的内部循环。这既更简单又更强大——例如,替换np.add的内部循环意味着你的代码将自动用于ndarray + ndarray以及对np.add的直接调用。所以这似乎不是不弃用set_numeric_ops的好理由。

讨论#