NEP 15 — 合并多数组和 umath #

作者

纳撒尼尔·史密斯 < 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 机制。

这两个模块各自公开自己单独的 C API,分别通过 import_multiarray()和进行访问import_umath()。这个想法是,它们应该是独立的模块,作为 构建在顶部的multiarray较低层。umath实际上,这已被证明是有问题的。

首先,分层并不完美:当您编写 时,会调用,然后调用 ufunc 。这意味着需要了解 ufunc——因此我们有一个循环依赖,而不是干净的分层。为了解决这个问题,导出了一个有点可怕的函数,称为.每次您的引导程序是:ndarray + ndarrayndarray.__add__np.addndarraymultiarrayset_numeric_opsimport numpy

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

  2. umath已加载。

  3. set_numeric_ops用于对所有方法进行猴子补丁,例如 ndarray.__add__来自 的对象umath

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

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

我们也越来越多地将实用程序代码放入 numpy/core/src/private/,它现在包含一堆文件,这些文件被#included 两次,一次放入multiarray,一次放入 umath。这是相当恶心的,并且纯粹是针对这些单独的 C 扩展的解决方法。该npymath库也包含在两个扩展模块中。

提议的更改#

该新经济政策提出了三项改变:

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

  2. 相反set_numeric_ops,我们应该使用一些新的、私有的 API 来设置ndarray.__add__和朋友。

  3. 我们应该弃用并最终删除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对象 - 但我们可以使这些模块中的一个或两个成为微型垫片,只需重新导出神奇的 API 对象,无论它实际定义在哪里。 (numpy/core/code_generators/generate_{numpy,ufunc}_api.py有关这些导入如何工作的详细信息,请参阅 参考资料。)

向后兼容性#

唯一的兼容性破坏是弃用np.set_numeric_ops.

拒绝的替代方案#

保留set_numeric_ops猴子补丁#

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

讨论