NEP 27 — 零秩数组#
- 作者:
Alexander Belopolsky (sasha),记录者 Matt Picus <matti.picus@gmail.com>
- 状态:
最终
- 类型:
信息性
- 创建时间:
2006-06-10
- 决议:
https://mail.python.org/pipermail/numpy-discussion/2018-October/078824.html
注意
NumPy 既有零秩数组,也有标量。这份设计文档改编自2006 年的 wiki 条目,描述了零秩数组是什么以及它们为何存在。它于 2018-10-13 被转录为 NEP,并更新了链接。该拉取请求引发了关于 NumPy 中零秩数组和标量持续必要性的热烈讨论。
这里的一些信息已经过时,例如 0-D 数组的索引现在已实现且不会报错。
零秩数组#
零秩数组是 shape=() 的数组。例如
>>> x = array(1)
>>> x.shape
()
零秩数组和数组标量#
数组标量在许多方面与零秩数组相似
>>> int_(1).shape
()
它们甚至打印出来也一样
>>> print int_(1)
1
>>> print array(1)
1
然而,它们之间存在一些重要的区别
数组标量是不可变的
数组标量对于不同的数据类型具有不同的 Python 类型
数组标量的动机#
NumPy 除了原生 Python 类型外,还提供了 0-d 数组和数组标量,这一设计决策违背了 Python 的一项基本设计原则,即做一件事应该只有一种显而易见的方法。在本节中,我们将尝试解释为什么有三种不同的方式来表示一个数字是必要的。
曾有几条 numpy-discussion 讨论串
关于零维数组与 Python 标量的思考,在2005 年的邮件列表线程中。
曾多次建议 NumPy 在所有情况下都使用秩-0 数组来表示标量。将秩-0 数组转换为标量的优缺点总结如下
优点
在某些情况下,当 Python 期望一个整数时(最显著的是对序列进行切片和索引时:_PyEval_SliceIndex 在 ceval.c 中),它不会在引发错误之前尝试将其首先转换为整数。因此,拥有一个能被数组对象自动转换为整数的 0-维数组是很方便的。
避免了两种类型之间几乎相同但不完全相同,且其独立存在只能用 Python 和 NumPy 开发历史来解释的用户混淆风险。
解决了进行显式类型检查的代码问题(
isinstance(x, float)
或type(x) == types.FloatType)
)。尽管显式类型检查通常被认为是糟糕的做法,但也有一些有效的原因需要使用它们。在 pickle 文件中不创建对 Numeric 的依赖(尽管这也可以通过为数组的 pickle 代码设置特殊情况来实现)
缺点
编写通用代码很困难,因为标量不具备与数组相同的方法和属性(例如
.type
或.shape
)。此外,Python 标量也有不同的数值行为。这导致了不愉快的特殊情况检查。从根本上说,它让用户相信多维同构数组某种程度上类似于 Python 列表(除了 Object 数组外,它们并非如此)。
NumPy 实现了一个旨在拥有上述所有优点而没有缺点的解决方案。
为所有 21 种类型创建 Python 标量类型,并继承已有的三种类型。为这些 Python 标量类型定义等效的方法和属性。
零秩数组的必要性#
一旦使用零秩数组表示标量的想法被否决,自然会考虑是否可以完全消除零秩数组。然而,在某些重要的用例中,零秩数组无法被数组标量替代。另请参阅 2006 年 2 月的“关于秩-0 数组的案例”。
输出参数
>>> y = int_(5) >>> add(5,5,x) array(10) >>> x array(10) >>> add(5,5,y) Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: return arrays must be of ArrayType
共享数据
>>> x = array([1,2]) >>> y = x[1:2] >>> y.shape = () >>> y array(2) >>> x[1] = 20 >>> y array(20)
零秩数组的索引#
截至 NumPy 0.9.3 版本,零秩数组不支持任何索引
>>> x[...]
Traceback (most recent call last):
File "<stdin>", line 1, in ?
IndexError: 0-d arrays can't be indexed.
另一方面,对于零秩数组,有几种情况是有意义的。
省略号和空元组#
Alexander 在 scipy-dev 上发起了2006 年 1 月的讨论,提出了以下建议:
……允许
a[...]
可能是合理的。这样,省略号就可以解释为任意数量的:
,包括零个。另一个对标量有意义的下标操作将是a[...,newaxis]
甚至a[{newaxis, }* ..., {newaxis,}*]
,其中{newaxis,}*
代表任意数量的逗号分隔的 newaxis 标记。这将允许在通用代码中使用省略号,使其适用于任何 numpy 类型。
Francesc Altet 支持在零秩数组上使用 [...]
的想法,并建议也支持 [()]
。
Francesc 的提案是:
In [65]: type(numpy.array(0)[...])
Out[65]: <type 'numpy.ndarray'>
In [66]: type(numpy.array(0)[()]) # Indexing a la numarray
Out[66]: <type 'int32_arrtype'>
In [67]: type(numpy.array(0).item()) # already works
Out[67]: <type 'int'>
对于零秩数组 x
,x[...]
和 x[()]
都应该是有效的,这一点已达成共识,但结果类型仍是一个问题——是零秩 ndarray 还是 x.dtype
?
- (亚历山大)
首先,无论对
x[...]
和x[()]
作何选择,它们都应相同,因为...
只是“根据需要添加任意数量的 :”的语法糖,对于零秩数组而言,这意味着... = (:,)*0 = ()
。其次,零秩数组和 numpy 标量类型在 numpy 内部可以互换,但 numpy 标量可以在某些 ndarray 不能使用的 Python 结构中使用。例如>>> (1,)[array(0)] Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: tuple indices must be integers >>> (1,)[int32(0)] 1
由于几乎所有(如果不是全部)numpy 函数在返回时都会自动将零秩数组转换为标量,因此 [...]
和 [()]
操作没有理由不同。
有关 x[...]
和 x[()]
返回 numpy 标量的实现,请参阅 SVN 变更集 1864(已成为 git 提交 9024ff0)。
有关 x[...] = v
和 x[()] = v
的实现,请参阅 SVN 变更集 1866(已成为 git 提交 743d922)。
使用 newaxis 增加秩#
所有评论者都喜欢这个功能,因此自 SVN 变更集 1871(已成为 git 提交 b32744e)起,任意数量的省略号和 newaxis 标记都可以作为零秩数组的下标参数。例如
>>> x = array(1)
>>> x[newaxis,...,newaxis,...]
array([[1]])
目前尚不清楚为何允许使用多个省略号,但这是我们试图保留的更高秩数组的行为。
重构#
目前,所有对零秩数组的索引都在一个特殊的 if (nd == 0)
代码分支中实现,该分支以前总是引发索引错误。这确保了更改不会影响任何现有用法(除了依赖异常的用法)。另一方面,这些更改的部分动机是为了使 ndarray 的行为更加统一,这应该允许完全消除 if (nd == 0)
检查。
版权#
原始文档出现在 scipy.org wiki 上,没有版权声明,其历史将其归因于 sasha。