NEP 27 — Zero rank arrays#
- 作者:
Alexander Belopolsky (sasha), transcribed 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 数组的索引现在已实现并且不会出错。
Zero-rank arrays#
零秩数组是 shape=() 的数组。例如
>>> x = array(1)
>>> x.shape
()
Zero-rank arrays and array scalars#
数组标量在许多方面与零秩数组相似
>>> int_(1).shape
()
它们甚至打印得一样
>>> print int_(1)
1
>>> print array(1)
1
但是存在一些重要区别
数组标量是不可变的
数组标量对不同的数据类型有不同的 Python 类型
Motivation for array scalars#
NumPy 除了原生 Python 类型外,还提供 0-D 数组和数组标量的设计决策,这违背了 Python 的一项基本设计原则,即“事情应该只有一个显而易见的方法”。在本节中,我们将尝试解释为什么需要三种不同的方式来表示一个数字。
曾有几次 numpy-discussion 讨论串
2002 年的邮件列表中关于 rank-0 arrays 的讨论。
在 2005 年的邮件列表中,关于零维数组与 Python 标量的思考。
曾多次有人建议 NumPy 在所有情况下都只使用 rank-0 数组来表示标量。将 rank-0 数组转换为标量的优缺点总结如下:
Pros
在 Python 期望一个整数的某些情况下(最显著的是切片和索引序列时:_PyEval_SliceIndex in ceval.c),它不会先尝试将其转换为整数,然后再引发错误。因此,拥有 0-D 数组并由数组对象为您转换整数会很方便。
没有因存在两种既相似又不完全相同的类型而引起用户混淆的风险,并且它们各自的存在只能用 Python 和 NumPy 开发的历史来解释。
没有代码显式类型检查的问题(
isinstance(x, float)或type(x) == types.FloatType)。尽管显式类型检查通常被认为是不好的做法,但也有一些合理的理由使用它们。不会在 pickle 文件中创建对 Numeric 的依赖(尽管也可以通过在数组的 pickling 代码中设置特例来完成)。
Cons
编写通用代码很困难,因为标量没有与数组相同的方法和属性(例如
.type或.shape)。此外,Python 标量在数值行为上也有所不同。这导致了不愉快的特殊情况检查。根本上,它让用户相信多维同质数组与 Python 列表(除了 Object 数组,它们不是)有点像。
NumPy 实现了一个解决方案,该解决方案旨在拥有所有优点而没有上述任何缺点。
为所有 21 种类型创建 Python 标量类型,并继承现有的三种类型。为这些 Python 标量类型定义等效的方法和属性。
The need for zero-rank arrays#
一旦排除了使用零秩数组来表示标量的想法,就自然而然地考虑是否可以完全消除零秩数组。但是,在某些重要用例中,零秩数组不能被数组标量替换。另请参阅 2006 年 2 月的 A case for rank-0 arrays。
Output arguments
>>> 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 ArrayTypeShared data
>>> x = array([1,2]) >>> y = x[1:2] >>> y.shape = () >>> y array(2) >>> x[1] = 20 >>> y array(20)
Indexing of zero-rank arrays#
从 NumPy release 0.9.3 开始,零秩数组不支持任何索引。
>>> x[...]
Traceback (most recent call last):
File "<stdin>", line 1, in ?
IndexError: 0-d arrays can't be indexed.
另一方面,有几种情况对 rank-zero 数组有意义。
Ellipsis and empty tuple#
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 标量可以在某些 Python 构造中使用,而 ndarrays 不能。例如>>> (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 changeset 1864(变为 git commit 9024ff0),其中实现了 x[...] 和 x[()] 返回 numpy 标量。
请参阅 SVN changeset 1866(变为 git commit 743d922),其中实现了 x[...] = v 和 x[()] = v。
Increasing rank with newaxis#
所有评论者都喜欢这项功能,因此从 SVN changeset 1871(变为 git commit b32744e)开始,可以在零秩数组的下标参数中放置任意数量的省略号和 newaxis 标记。例如:
>>> x = array(1)
>>> x[newaxis,...,newaxis,...]
array([[1]])
不清楚为什么允许使用多个省略号,但这是我们试图保留的高秩数组的行为。
Refactoring#
目前,所有对零秩数组的索引都在特殊的 if (nd == 0) 代码分支中实现,该分支过去总是引发索引错误。这确保了更改不会影响任何现有用法(除了依赖于异常的用法)。另一方面,这些更改的部分动机是为了使 ndarrays 的行为更加统一,这应该允许完全消除 if (nd == 0) 检查。
版权#
原始文档出现在 scipy.org wiki 上,没有版权声明,其 历史记录将其归功于 sasha。