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.multiarray
和 numpy.core.umath
合并到单个扩展模块中,并弃用 np.set_numeric_ops
。
背景#
目前,numpy 的核心 C 代码分为两个独立的扩展模块。
numpy.core.multiarray
由 numpy/core/src/multiarray/*.c
构建,包含核心数组功能(特别是 ndarray
对象)。
numpy.core.umath
由 numpy/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
时的引导过程是:
multiarray
及其ndarray
对象已加载,但 ndarray 的算术运算已损坏。umath
已加载。使用
set_numeric_ops
用umath
中的对象来修补所有方法,例如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 提出三个更改:
我们应该开始将
numpy/core/src/multiarray/*.c
和numpy/core/src/umath/*.c
构建到单个扩展模块中。我们应该使用一些新的私有 API 来设置
ndarray.__add__
及其相关方法,而不是set_numeric_ops
。我们应该弃用并最终删除
np.set_numeric_ops
。
未提出的更改#
我们不一定建议在我们的源代码组织方面抛弃 multiarray/ 和 umath/ 之间的区别:内部组织很有用!我们只是想将它们一起构建到单个扩展模块中。当然,这为潜在的未来重构打开了大门,然后我们可以根据它们出现的优点来评估它们。
它也没有提出要破坏公共 C ABI。我们应该继续提供import_multiarray()
和import_umath()
函数——只是现在这两个ABI最终都将从同一个C库加载。由于import_multiarray()
和import_umath()
的编写方式,我们仍然需要名为numpy.core.multiarray
和numpy.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
的好理由。
讨论#
版权#
本文档已进入公共领域。