NEP 41 — 新数据类型系统的第一步#
- 标题:
迈向新的数据类型系统的第一步
- 作者:
Sebastian Berg
- 作者:
Stéfan van der Walt
- 作者:
Matti Picus
- 状态:
已接受
- 类型:
标准轨道
- 创建:
2020-02-03
- 决议:
https://mail.python.org/pipermail/numpy-discussion/2020-April/080573.html 和 https://mail.python.org/pipermail/numpy-discussion/2020-March/080495.html
注意
此 NEP 是系列中的第二个
摘要#
数据类型 在 NumPy 中描述了如何解释数组中的每个元素。NumPy 提供了int
、float
和 complex
数值类型,以及字符串、日期时间和结构化数据类型功能。然而,不断发展的 Python 社区需要更多样化的数据类型。例如,带有附加单位信息(例如米)的数据类型或分类数据类型(固定的一组可能值)。但是,当前的 NumPy 数据类型 API 太有限,无法创建这些数据类型。
此 NEP 是实现这种增长的第一步;它将带来更简单的开发路径以创建新的数据类型。从长远来看,新的数据类型系统还将支持直接从 Python 而不是 C 创建数据类型。重构数据类型 API 将提高可维护性,并促进开发用户定义的外部数据类型以及 NumPy 内部现有数据类型的功能。
动机和范围#
另请参见
用户影响部分包含从长远来看,提议的更改将启用哪些新型数据类型的示例。因此,可能需要按不同顺序阅读这些部分。
动机#
当前 API 的主要问题之一是为参数化数据类型定义典型的函数,例如加法和乘法(另见NEP 40),这需要额外的步骤来确定输出类型。例如,当添加两个长度为 4 的字符串时,结果是长度为 8 的字符串,这与输入不同。同样,嵌入物理单位的数据类型必须计算新的单位信息:距离除以时间得到速度。当前的转换规则 – 不同数据类型之间的转换 – 无法描述 NumPy 外部实现的此类参数化数据类型的转换。
支持参数化数据类型的此附加功能增加了 NumPy 本身内部的复杂性,此外,外部用户定义的数据类型也无法使用此功能。一般而言,不同数据类型的关注点并未很好地封装。由于暴露了内部 C 结构,这种负担加剧了,从而限制了添加新字段(例如,支持新的排序方法[new_sort])。
目前,许多因素限制了创建新的用户定义数据类型
为参数化用户定义的 dtype 创建转换规则要么不可能,要么复杂到从未尝试过。
类型提升,例如决定将浮点数和整数值相加应返回浮点值的运算,对于数值数据类型非常有价值,但在用户定义的和特别是参数化数据类型的范围内受到限制。
许多逻辑(例如提升)都写在单个函数中,而不是作为数据类型本身的方法进行拆分。
在当前设计中,数据类型不能具有不能推广到其他数据类型的方法。例如,单位数据类型不能具有
.to_si()
方法来轻松查找将以 SI 单位表示相同值的数据类型。
解决这些问题的巨大需求促使科学界在多个项目中创建解决方法,这些项目将物理单位实现为类似数组的类而不是数据类型,这将更好地概括跨多个类似数组(Dask、pandas 等)。Pandas 已经使用其扩展数组[pandas_extension_arrays] 向相同的方向迈进了一步,毫无疑问,如果 NumPy、Pandas 和其他项目之间可以共享此类新功能,那么社区将得到最好的服务。
范围#
拟议的数据类型系统重构是一项庞大的工作,因此建议将其拆分为不同的阶段,大致如下:
第一阶段:重构和扩展数据类型基础结构(此 NEP 41)
第二阶段:逐步定义或修改 API(主要在 NEP 42/43 中详细说明)
第三阶段:NumPy 和科学 Python 生态系统能力的增长。
有关各个阶段的更详细说明,请参阅下面“实现”部分中的“全面重构计划”。本 NEP 建议继续进行必要的新的 dtype 子类创建(第一阶段),并开始着手实现当前功能。在本 NEP 的上下文中,所有开发都将完全是私有 API 或使用初步的下划线命名,这些命名将来必须更改。大多数内部和公共 API 选择属于第二阶段,将在接下来的 NEP 42 和 43 中更详细地讨论。NEP 的初始实现对用户几乎没有影响,但为逐步解决全面重构工作提供了必要的基石。
本 NEP 及其后续实现,暗示着对 NumPy 中数据类型定义方式的大规模重构,预计会产生一些不兼容性(请参阅向后兼容性部分)。但是,不需要并且也不在范围内进行需要大量代码调整的转换。
具体来说,本 NEP 做出了以下设计选择,这些选择在详细描述部分中有更详细的讨论
每个数据类型都将是
np.dtype
子类的实例,大多数特定于数据类型的逻辑都将作为类的特殊方法实现。在 C-API 中,这些对应于特定的槽。简而言之,对于f = np.dtype("f8")
,isinstance(f, np.dtype)
将保持为真,但type(f)
将是np.dtype
的子类,而不仅仅是np.dtype
本身。PyArray_ArrFuncs
目前存储在实例上的指针上(作为PyArray_Descr->f
),应该改为像 Python 中通常那样存储在类上。将来,这些可能对应于 Python 侧的 dunder 方法。诸如 itemsize 和 byteorder 之类的存储信息在不同的 dtype 实例(例如“S3”与“S8”)之间可能有所不同,并将保留为实例的一部分。这意味着从长远来看,当前对 dtype 方法的底层访问将被移除(请参阅 NEP 40 中的PyArray_ArrFuncs
)。当前的 NumPy 标量 *不会* 改变,它们不会是数据类型的实例。对于新的数据类型,这也将是正确的,标量不会是 dtype 的实例(尽管当合适时,可以使
isinstance(scalar, dtype)
返回True
)。
详细的技术决策将在 NEP 42 中进行。
此外,公共 API 的设计方式将在未来具有可扩展性。
提供给用户的所有新的 C-API 函数都将尽可能隐藏实现细节。公共 API 应该是内部 NumPy 数据类型使用的 C-API 的相同但受限的版本。
数据类型系统可能以与 NumPy 数组一起工作为目标,例如通过提供步幅循环,但应避免与数组对象(通常是 np.ndarray 实例)直接交互。相反,设计原则将是数组对象是数据类型的使用者。虽然只是一个指导原则,但这可以允许将数据类型系统甚至 NumPy 数据类型拆分为 NumPy 依赖的独立项目。
第二阶段中对数据类型系统的更改必须包括对 UFunc 机制的大规模重构,这将在 NEP 43 中进一步定义。
为了实现对新的用户定义数据类型所需的所有功能,将更改 UFunc 机制以替换当前的调度和类型解析系统。旧系统应该在一段时间内作为遗留版本 *主要* 受支持。
此外,作为一般设计原则,添加新的用户定义数据类型 *不会* 更改程序的行为。例如,common_dtype(a, b)
不得为 c
,除非 a
或 b
知道 c
存在。
用户影响#
当前的生态系统很少使用 NumPy 的用户定义数据类型,其中最突出的两个是:rational
和 quaternion
。这些表示相当简单的数据类型,不会受到当前限制的强烈影响。但是,我们已经确定需要以下数据类型:
bfloat16,用于深度学习
分类类型
物理单位(例如米)
用于跟踪/自动微分的数据类型
高精度、定点数学
专门的整数类型,例如 int2、int24
新的、更好的日期时间表示
例如扩展整数 dtype 以具有哨兵 NA 值
几何对象 [pygeos]
其中一些问题已部分解决;例如,astropy.units
、unyt
或 pint
中提供了单位功能,作为 numpy.ndarray 的子类。但是,大多数这些数据类型目前根本无法合理定义。在 NumPy 中拥有此类数据类型的优势在于,它们应该与 Pandas、xarray
[xarray_dtype_issue] 或 Dask
等其他数组或类似数组的包无缝集成。
实施本 NEP 的长期用户影响将是通过拥有此类新的数据类型来促进整个生态系统的增长,以及在 NumPy 中巩固此类数据类型的实现以实现更好的互操作性。
示例#
以下示例代表我们希望启用的未来用户定义的数据类型。这些数据类型不是 NEP 和选择(例如,选择转换规则)的一部分,是我们希望启用的可能性,并不代表建议。
简单的数值类型#
主要用于内存受限的情况,低精度数值类型(例如 bfloat16)在其他计算框架中很常见。对于这些类型,np.common_type
和 np.can_cast
等内容的定义是一些最重要的接口。一旦它们支持 np.common_type
,就可以(在大多数情况下)找到要调用的正确的 ufunc 循环,因为大多数 ufunc(例如 add)实际上只需要 np.result_type
>>> np.add(arr1, arr2).dtype == np.result_type(arr1, arr2)
而 ~numpy.result_type 与 ~numpy.common_type 在很大程度上是相同的。
固定精度、高精度数学#
允许任意精度或更高精度的数学在模拟中很重要。例如,mpmath
定义了一个精度
>>> import mpmath as mp
>>> print(mp.dps) # the current (default) precision
15
NumPy 应该能够从 mpmath.mpf
浮点数对象的列表中构建本地的、内存高效的数组。
>>> arr_15_dps = np.array(mp.arange(3)) # (mp.arange returns a list)
>>> print(arr_15_dps) # Must find the correct precision from the objects:
array(['0.0', '1.0', '2.0'], dtype=mpf[dps=15])
我们还应该能够在为数组创建数据类型时指定所需的精度。在这里,我们使用 np.dtype[mp.mpf]
来查找 DType 类(该表示法不是本 NEP 的一部分),然后使用所需的参数对其进行实例化。这也可以写成 MpfDType
类。
>>> arr_100_dps = np.array([1, 2, 3], dtype=np.dtype[mp.mpf](dps=100))
>>> print(arr_15_dps + arr_100_dps)
array(['0.0', '2.0', '4.0'], dtype=mpf[dps=100])
mpf
数据类型可以确定操作的结果应该是两者中精度较高的一个,因此使用 100 的精度。此外,我们应该能够定义转换,例如:
>>> np.can_cast(arr_15_dps.dtype, arr_100_dps.dtype, casting="safe")
True
>>> np.can_cast(arr_100_dps.dtype, arr_15_dps.dtype, casting="safe")
False # loses precision
>>> np.can_cast(arr_100_dps.dtype, arr_100_dps.dtype, casting="same_kind")
True
从浮点数进行转换可能总是至少是 same_kind
转换,但总的来说,它是不安全的
>>> np.can_cast(np.float64, np.dtype[mp.mpf](dps=4), casting="safe")
False
因为 float64 的精度高于 mpf
数据类型,其 dps=4
。
或者,我们可以说:
>>> np.common_type(np.dtype[mp.mpf](dps=5), np.dtype[mp.mpf](dps=10))
np.dtype[mp.mpf](dps=10)
甚至可能:
>>> np.common_type(np.dtype[mp.mpf](dps=5), np.float64)
np.dtype[mp.mpf](dps=16) # equivalent precision to float64 (I believe)
因为 np.float64
可以安全地转换为 np.dtype[mp.mpf](dps=16)
。
分类#
分类的有趣之处在于它们可以具有固定、预定义的值,或者可以是动态的,并且能够根据需要修改类别。预先定义的固定类别是最直接的分类定义。分类是 *困难的*,因为实现它们有很多策略,这表明 NumPy 应该只为用户定义的分类类型提供脚手架。例如:
>>> cat = Categorical(["eggs", "spam", "toast"])
>>> breakfast = array(["eggs", "spam", "eggs", "toast"], dtype=cat)
可以非常高效地存储数组,因为它知道只有 3 个类别。由于从这种意义上讲的分类几乎不知道存储在其中的数据,因此很少有操作有意义,尽管相等性有意义。
>>> breakfast2 = array(["eggs", "eggs", "eggs", "eggs"], dtype=cat)
>>> breakfast == breakfast2
array[True, False, True, False])
分类数据类型可以像字典一样工作:没有两个项目名称可以相等(在创建数据类型时检查),以便可以非常高效地执行上述相等性操作。如果值定义了顺序,则类别标签(内部整数)可以按相同方式排序以允许高效的排序和比较。
是否从具有较少值到具有严格更多值定义的分类定义转换,是分类数据类型需要决定的问题。两种选择都应该可用。
数据类型上的单位#
根据内部机制的组织方式,定义单位的方法有很多种,一种方法是为每种现有的数值类型都使用单个单位数据类型。这将写成 Unit[float64]
,单位本身是 DType 实例的一部分,Unit[float64]("m")
是带有米附加的 float64
。
>>> from astropy import units
>>> meters = np.array([1, 2, 3], dtype=np.float64) * units.m # meters
>>> print(meters)
array([1.0, 2.0, 3.0], dtype=Unit[float64]("m"))
请注意,单位有点棘手。是否
>>> np.array([1.0, 2.0, 3.0], dtype=Unit[float64]("m"))
应该是有效的语法(强制没有单位的浮点标量为米)是有争议的。创建数组后,数学运算将没有任何问题。
>>> meters / (2 * unit.seconds)
array([0.5, 1.0, 1.5], dtype=Unit[float64]("m/s"))
从一个单位到另一个单位的转换是无效的,但在相同维度的不同比例之间可能是有效的(尽管这可能是“不安全的”)。
>>> meters.astype(Unit[float64]("s"))
TypeError: Cannot cast meters to seconds.
>>> meters.astype(Unit[float64]("km"))
>>> # Convert to centimeter-gram-second (cgs) units:
>>> meters.astype(meters.dtype.to_cgs())
上述表示法有点笨拙。可以使用函数来在单位之间进行转换。可能有一些方法可以使这些更方便,但这必须留待以后讨论。
>>> units.convert(meters, "km")
>>> units.to_cgs(meters)
还有一些悬而未决的问题。例如,数组对象上是否存在其他方法可以简化某些概念,以及这些方法如何从数据类型传递到 ndarray
。
与其他标量的交互很可能通过
>>> np.common_type(np.float64, Unit)
Unit[np.float64](dimensionless)
Ufunc 输出数据类型确定可能比简单的数值 dtype 更复杂,因为没有“通用”输出类型。
>>> np.multiply(meters, seconds).dtype != np.result_type(meters, seconds)
事实上,np.result_type(meters, seconds)
在没有操作上下文的情况下必须报错。此示例突出了特定 ufunc 循环(具有已知特定 DType 作为输入的循环)必须能够在实际计算开始之前做出某些决策。
实现#
全面重构的计划#
为了解决 NumPy 中的这些问题并启用新的数据类型,需要多个开发阶段
第一阶段:重构和扩展数据类型基础设施(本 NEP)
像普通的 Python 类一样组织数据类型 [PR 15508]_
第二阶段:逐步定义或修改 API
通过 DType 上的方法和属性逐步定义所有必要的函数(NEP 42)
类层次结构和 DType 类本身的属性,包括以下最重要点未涵盖的方法。
支持使用
arr.astype()
进行 dtype 类型转换以及np.common_type
等相关操作的函数。项目访问和存储的实现,以及使用
np.array()
创建数组时形状和 dtype 的确定方式。创建一个公共 C-API 来定义新的 DType。
重构通用函数的工作方式(NEP 43),以允许扩展~numpy.ufunc(例如
np.add
)以支持用户定义的数据类型,例如 Units。重构底层 C 函数的组织方式,使其具有足够的扩展性和灵活性,以适应 Units 等复杂 DType。
实现这些底层 C 函数的注册和高效查找,如用户定义的那样。
定义如何在需要类型转换时使用提升来实现行为。例如
np.float64(3) + np.int32(3)
将int32
提升为float64
。
第三阶段:NumPy 和科学 Python 生态系统能力的增长
清理被认为有错误或不良行为的旧版行为。
提供从 Python 定义新数据类型的路径。
协助社区创建诸如 Units 或 Categoricals 之类的类型。
允许在
np.equal
或np.add
等函数中使用字符串。删除 NumPy 中的旧代码路径,以提高长期可维护性。
本文档作为第一阶段的基础,并为整个项目提供愿景和动机。第一阶段不会引入任何新的面向用户的特性,而是关注当前数据类型系统的必要概念清理。它提供了一个更“pythonic”的数据类型 Python 类型对象,具有清晰的类层次结构。
第二阶段是逐步创建定义完整功能数据类型所需的所有 API,以及重组 NumPy 数据类型系统。因此,此阶段主要关注定义一个最初的、初步的稳定公共 API。
全面重构的一些好处可能只有在当前旧版实现完全弃用后才会显现(即更大的代码删除)。但是,这些步骤对于改进核心 NumPy API 的许多部分是必要的,并且有望使实现更容易理解。
下图以高层次说明了建议的设计,并大致描绘了整体设计的组成部分。请注意,本 NEP 只涉及第一阶段(阴影区域),其余部分包括第二阶段,设计选择尚待讨论,但是,它强调了 DType 数据类型类是中心必要的概念。
向后兼容性#
虽然实现第一阶段和第二阶段的实际向后兼容性影响尚不清楚,但我们预计并接受以下更改
Python API:
type(np.dtype("f8"))
将是np.dtype
的子类,而现在type(np.dtype("f8")) is np.dtype
。代码应该使用isinstance
检查,并且在极少数情况下可能必须进行调整才能使用它。
C-API:
在旧版本的 NumPy 中,
PyArray_DescrCheck
是一个使用type(dtype) is np.dtype
的宏。当针对旧版 NumPy 版本编译时,可能必须将宏替换为相应的PyObject_IsInstance
调用。(如果这是一个问题,我们可以回溯修复宏)UFunc 机制更改将破坏当前实现的有限部分。例如,替换默认的
TypeResolver
预计会在一段时间内继续受支持,尽管优化的掩码内部循环迭代(甚至在 NumPy 内也没有使用)将不再受支持。当前在 dtype 上定义的所有函数,例如
PyArray_Descr->f->nonzero
,将以不同的方式定义和访问。这意味着从长远来看,低级访问代码将不得不更改为使用新的 API。预计在很少的项目中需要进行此类更改。
dtype 实现者(C-API):
当前提供给某些函数(例如转换函数)的数组将不再提供。例如
PyArray_Descr->f->nonzero
或PyArray_Descr->f->copyswapn
,可能会改为接收一个虚拟数组对象,其中只有某些字段(主要是 dtype)有效。至少在某些代码路径中,已经使用了类似的机制。scalarkind
槽和标量转换的注册将被删除/忽略,不会被替换。它目前允许基于值的局部转换。PyArray_ScalarKind
函数将继续适用于内置类型,但不会在内部使用,并将被弃用。当前用户 dtype 被定义为
np.dtype
的实例。创建过程是用户提供原型实例。NumPy 将需要至少在注册期间修改类型。这对rational
或quaternion
都没有影响,并且在注册后修改结构似乎不太可能。
由于关于数据类型的 API 表面积相当大,因此可能会发生进一步的更改或将某些函数限制为当前存在的数据类型。例如,使用类型号作为输入的函数应该替换为使用 DType 类作为输入的函数。虽然是公共的,但此 C-API 的大部分似乎很少使用,可能从未被下游项目使用。
详细描述#
本节详细介绍了本 NEP 中涵盖的设计决策。小节对应于范围部分中提出的设计选择列表。
数据类型作为 Python 类 (1)#
当前的 NumPy 数据类型不是完整的 Python 类。它们而是单个np.dtype
类的(原型)实例。更改这一点意味着任何特殊处理(例如,对于datetime
)都可以移动到 Datetime DType 类,而不是单片通用代码(例如,当前的PyArray_AdjustFlexibleDType
)。
关于 API 的此更改的主要结果是特殊方法从 dtype 实例移动到新 DType 类上的方法。这是 Python 中使用的典型设计模式。以更 Pythonic 的方式组织这些方法和信息为将来改进和扩展 API 提供了坚实的基础。当前的 API 由于其公开方式而无法扩展。这意味着例如,当前存储在每个数据类型上的PyArray_ArrFuncs
中的方法(参见NEP 40)将来将以不同的方式定义,并最终被弃用。
最显著的可见副作用是type(np.dtype(np.float64))
不再是np.dtype
。它将成为np.dtype
的子类,这意味着isinstance(np.dtype(np.float64), np.dtype)
仍然为真。这也增加了使用isinstance(dtype, np.dtype[float64])
的能力,从而无需使用dtype.kind
、dtype.char
或dtype.type
进行此检查。
由于将DType设计为完整的Python类,因此出现了子类化的问题。然而,继承似乎存在问题,并且对于容器数据类型来说,最好避免这种复杂性(至少在最初)。此外,子类对于互操作性可能更有趣,例如与GPU后端(CuPy)的互操作性,存储与GPU相关的附加方法,而不是作为定义新数据类型的机制。类层次结构确实提供了价值,可以通过允许创建_抽象_数据类型来实现。抽象数据类型的示例是np.floating
的等效数据类型,表示任何浮点数。它们可以与Python的抽象基类起到相同的作用。
本NEP选择完全或部分复制标量层次结构。主要原因是解耦DType和标量的实现。从理论上讲,要向NumPy添加DType,不需要修改标量或让标量了解NumPy。另请注意,当前在pandas中实现的分类DType没有标量对应关系,这使得依赖标量来实现行为不太直接。虽然DType和Scalar描述的是相同的概念/类型(例如int64),但将NumPy所需的信息和功能分离到DType类中似乎是可行的。
dtype实例提供参数和存储选项#
从计算机科学的角度来看,类型定义_值空间_(其实例可以取的所有可能值)及其_行为_。正如本NEP中提出的,DType类定义值空间和行为。dtype
实例可以看作是值的一部分,因此典型的Python instance
对应于dtype + element
(其中_element_是存储在数组中的数据)。另一种观点是直接在dtype
实例上定义值空间和行为。下图显示了这两个选项,并将其与类似的Python实现模式进行了比较。
不同之处在于如何处理参数(例如字符串长度或日期时间单位(ms
、ns
……))和存储选项(例如字节序)。在实现Python(标量)type
参数时,例如日期时间单位,将存储在实例中。这是NEP 42试图模仿的设计,但是,参数现在是dtype实例的一部分,这意味着实例中存储的部分数据由所有数组元素共享。如前所述,这意味着Python instance
对应于存储在NumPy数组中的dtype + element
。
Python中一种更高级的方法是使用类工厂和抽象基类(ABC)。这允许将参数移动到动态创建的type
中,并且行为实现可能特定于这些参数。另一种方法可能会使用此模型并在dtype
实例上直接实现行为。
我们认为此处提出的版本更容易使用和理解。Python类工厂并不常用,NumPy也不使用专门用于dtype参数或字节序的代码。使这种专门化更容易实现似乎并不是优先事项。这种选择的其中一个结果是,如果某些DType没有参数或存储变化,则它们可能只有一个单例实例。但是,由于允许附加元数据,所有NumPy dtype都需要动态创建实例。
标量不应是数据类型的实例 (2)#
对于float64
等简单数据类型(另见下文),np.dtype("float64")
的实例可以作为标量,这似乎很诱人。由于标量(而不是数据类型)目前定义了一个有用的类型层次结构,这个想法可能更具吸引力。
但是,我们出于多种原因特别反对这样做。首先,本文档中描述的新数据类型将是DType类的实例。虽然可以使这些实例本身成为类,但这增加了用户需要理解的额外复杂性。这也意味着标量必须具有存储信息(例如字节序),这通常是不必要的,目前也没有使用。其次,虽然float64
之类的简单NumPy标量可以是此类实例,但应该可以为Python对象创建数据类型,而无需强制NumPy作为依赖项。但是,不依赖NumPy的Python对象不能是NumPy DType的实例。第三,标量和数据类型之间有用的方法和属性不匹配。例如,to_float()
对标量有意义,但对数据类型则没有意义,而newbyteorder
对标量没有用(或含义不同)。
总的来说,与其降低复杂性(例如,通过合并两个不同的类型层次结构),不如说使标量成为DType的实例会增加设计和实现的复杂性。
未来的一个可能路径是简化当前的NumPy标量,使其成为更简单的对象,其行为主要源于数据类型。
创建新数据类型的C-API (3)#
用户可以使用当前的C-API创建新数据类型,但其范围有限,并且需要使用“私有”结构。这意味着API不可扩展:在不丢失二进制兼容性的情况下,无法向结构添加新成员。这已经限制了将新的排序方法包含到NumPy中 [new_sort]。
因此,新版本将替换当前用于定义新数据类型的PyArray_ArrFuncs
结构。在弃用期间,将支持当前存在并使用这些槽定义的数据类型。
最可能的解决方案是从用户那里隐藏实现,从而使其将来可扩展,方法是根据Python的稳定API建模API [PEP-384]
static struct PyArrayMethodDef slots[] = {
{NPY_dt_method, method_implementation},
...,
{0, NULL}
}
typedef struct{
PyTypeObject *typeobj; /* type of python scalar */
...;
PyType_Slot *slots;
} PyArrayDTypeMeta_Spec;
PyObject* PyArray_InitDTypeMetaFromSpec(
PyArray_DTypeMeta *user_dtype, PyArrayDTypeMeta_Spec *dtype_spec);
C端槽的设计应与Python端方法(例如dtype.__dtype_method__
)相对应,尽管对Python的公开是实现的后续步骤,以降低初始实现的复杂性。
对UFunc机制的C-API更改 (4)#
对UFunc机制的建议更改将是NEP 43的一部分。但是,以下更改是必要的(有关当前实现及其问题的详细描述,请参见NEP 40)
必须调整当前的UFunc类型解析,以允许更好地控制用户定义的dtype以及解决当前的不一致性。
UFunc中使用的内部循环必须扩展以包含返回值。此外,必须改进错误报告,并启用传递dtype特定信息。这需要修改内部循环函数签名并添加在使用内部循环之前和之后调用的新钩子。
对通用函数进行任何更改的一个重要目标是允许重用现有循环。对于新的单位数据类型来说,在处理与单位相关的计算后,回退到现有的数学函数应该很容易。
讨论#
有关以前的会议和讨论的列表,请参见NEP 40。
围绕此特定NEP的额外讨论已在邮件列表和拉取请求中进行。
参考文献#
版权#
本文档已进入公共领域。
致谢#
多年来,在许多不同的背景和环境中,人们一直在讨论为NumPy创建新数据类型的努力,因此不可能列出所有参与者。我们要特别感谢Stephan Hoyer、Nathaniel Smith和Eric Wieser对数据类型设计的反复深入讨论。我们非常感谢社区在审查和修改本NEP方面的投入,并要特别感谢Ross Barnowski和Ralf Gommers。