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 年的维基条目,描述了零秩数组是什么以及它们存在的原因。它于 2018 年 10 月 13 日被转录成 NEP,并且链接已更新。该拉取请求引发了 关于 NumPy 中继续需要零秩数组和标量的热烈讨论。
这里的一些信息已过时,例如 0 维数组的索引现在已实现,不会再报错。
零秩数组#
零秩数组是形状为 () 的数组。例如
>>> x = array(1)
>>> x.shape
()
零秩数组和数组标量#
数组标量在许多方面与零秩数组类似
>>> int_(1).shape
()
它们甚至打印相同的内容
>>> print int_(1)
1
>>> print array(1)
1
但是,存在一些重要的区别
数组标量是不可变的
数组标量针对不同的数据类型具有不同的 Python 类型
数组标量的动机#
NumPy 的设计决定是在原生 Python 类型之外提供 0 维数组和数组标量,这违背了 Python 的基本设计原则之一,即应该只有一种明显的方式来完成某件事。在本节中,我们将尝试解释为什么有必要用三种不同的方式来表示一个数字。
有几个关于 numpy-discussion 的讨论主题
秩 0 数组 在 2002 年的邮件列表主题中。
关于零维数组与 Python 标量的想法,在 2005 年的邮件列表主题 中。
已经多次建议 NumPy 仅使用秩 0 数组来表示所有情况下的标量。将秩 0 数组转换为标量的优缺点如下总结:
优点
在 Python 期望整数的一些情况下(最明显的情况是在对序列进行切片和索引时:_PyEval_SliceIndex 在 ceval.c 中),它不会先尝试将其转换为整数,然后再抛出错误。因此,拥有由数组对象自动为您转换的 0 维数组非常方便。
没有因为拥有两种类型(几乎相同但并不完全相同,并且它们的存在只能用 Python 和 NumPy 开发的历史来解释)而导致用户混淆的风险。
没有针对使用显式类型检查的代码出现问题
(isinstance(x, float)
或type(x) == types.FloatType)
。虽然显式类型检查通常被认为是不好的做法,但确实存在几个正当理由可以使用它们。不会在 pickle 文件中创建对 Numeric 的依赖关系(尽管这也可以通过在 pickle 代码中为数组添加特殊情况来实现)。
缺点
难以编写通用代码,因为标量没有与数组相同的 methods 和 attributes(例如
.type
或.shape
)。此外,Python 标量的数值行为也不同。这导致了令人不快的特殊情况检查。从根本上说,它让用户相信多维同质数组与 Python 列表有些相似(除了对象数组,它们并不相似)。
NumPy 实现了一种旨在兼顾以上所有优点而没有缺点的解决方案。
为所有 21 种类型创建 Python 标量类型,并从已存在的 3 种类型继承。为这些 Python 标量类型定义等效的 methods 和 attributes。
零秩数组的必要性#
在拒绝使用零秩数组来表示标量的想法之后,自然要考虑是否可以完全消除零秩数组。然而,在一些重要用例中,零秩数组无法用数组标量替换。另见 秩 0 数组的案例,来自 2006 年 2 月。
输出参数
>>> 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 在 2006 年 1 月的 scipy-dev 上发起了 讨论,提出了以下提案:
… 允许
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
?
- (Alexander)
首先,无论对
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 函数在返回时会自动将零秩数组转换为标量,因此没有理由让 [...]
和 [()]
运算不同。
请参阅 SVN 更改集 1864(已成为 git 提交 9024ff0)以了解 x[...]
和 x[()]
返回 numpy 标量的实现。
请参阅 SVN 更改集 1866(已成为 git 提交 743d922)以了解 x[...] = v
和 x[()] = v
的实现。
使用 newaxis 提高秩#
所有评论者都喜欢这个功能,因此从 SVN 更改集 1871(已成为 git 提交 b32744e)开始,可以在零秩数组的下标参数中放置任意数量的省略号和 newaxis 标记。例如
>>> x = array(1)
>>> x[newaxis,...,newaxis,...]
array([[1]])
目前尚不清楚为什么应该允许出现多个省略号,但这是我们试图保留的更高秩数组的行为。
重构#
目前,对零秩数组的所有索引都在一个特殊的 if (nd == 0)
代码分支中实现,该分支过去总是会引发索引错误。这确保了这些更改不会影响任何现有的用法(除了依赖异常的用法)。另一方面,这些更改的部分动机是使 ndarray 的行为更统一,这应该允许完全消除 if (nd == 0)
检查。
版权#
原始文档出现在 scipy.org 维基上,没有版权声明,并且它的 历史记录 将其归功于 sasha。