在numpy中,使用空元组和省略号索引数组有什么区别?
我偶然发现,numpy
中的数组可以用一个空的元组来索引:
In [62]: a = arange(5)
In [63]: a[()]
Out[63]: array([0, 1, 2, 3, 4])
我在numpy的wiki上找到了一些关于零维数组的文档:
(Sasha) 首先,无论你选择用 x[...] 还是 x[()],它们应该是一样的,因为 ... 只是一个语法糖,意思是“根据需要使用多少个 :”,在零维的情况下,这就变成了 ... = (:,)*0 = ()。其次,零维数组和numpy标量类型在numpy中是可以互换的,但numpy标量可以在一些python的结构中使用,而ndarray则不能。
所以,对于0维数组来说,a[()]
和 a[...]
应该是等价的。那么对于更高维的数组呢?它们似乎也是:
In [65]: a = arange(25).reshape(5, 5)
In [66]: a[()] is a[...]
Out[66]: False
In [67]: (a[()] == a[...]).all()
Out[67]: True
In [68]: a = arange(3**7).reshape((3,)*7)
In [69]: (a[()] == a[...]).all()
Out[69]: True
但是,这并不是语法糖。对于高维数组来说,甚至对于0维数组来说也是如此:
In [76]: a[()] is a
Out[76]: False
In [77]: a[...] is a
Out[77]: True
In [79]: b = array(0)
In [80]: b[()] is b
Out[80]: False
In [81]: b[...] is b
Out[81]: True
还有一种情况是用空列表进行索引,这完全是另一回事,但看起来和用空的ndarray
索引是等价的:
In [78]: a[[]]
Out[78]: array([], shape=(0, 3, 3, 3, 3, 3, 3), dtype=int64)
In [86]: a[arange(0)]
Out[86]: array([], shape=(0, 3, 3, 3, 3, 3, 3), dtype=int64)
In [82]: b[[]]
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
IndexError: 0-d arrays can't be indexed.
所以,看起来()
和...
是相似的,但并不完全相同,而用[]
进行索引则完全是另一种意思。a[]
或b[]
会导致SyntaxError
错误。关于用列表进行索引的文档可以在索引数组中找到,还有一段简短的说明关于用元组进行索引的内容,可以在同一文档的末尾找到。
这就留下了一个问题:
a[()]
和a[...]
之间的区别是设计使然吗?那么这个设计是什么呢?
(这个问题让人想起了:空的 `()` 在Matlab矩阵中有什么作用?)
编辑:
事实上,甚至标量也可以用空元组进行索引:
In [36]: numpy.int64(10)[()]
Out[36]: 10
3 个回答
根据官方的Numpy文档,这两者的区别很明显:
一个空的(元组)索引是对一个零维数组的完整标量索引。
x[()]
如果x
是零维的,返回一个标量值;如果不是,则返回一个视图。 另一方面,x[...]
总是返回一个视图。当出现省略号(
...
)但没有大小(也就是说,替代了零:
)时,结果仍然总是一个数组。如果没有高级索引,则返回一个视图;否则返回一个副本。
>>> import numpy as np
>>> # ---------------------------------- #
>>> # when `x` is at least 1 dimensional #
>>> # ---------------------------------- #
>>> x = np.linspace(0, 10, 100)
>>> x.shape
(100,)
>>> x.ndim
1
>>> a = x[()]
>>> b = x[...]
>>> id(x), id(a), id(b)
(4559933568, 4561560080, 4585410192)
>>> id(x.base), id(a.base), id(b.base)
(4560914432, 4560914432, 4560914432)
>>> # ---------------------------- #
>>> # when `z` is zero dimensional #
>>> # ---------------------------- #
>>> z = np.array(3.14)
>>> z.shape
()
>>> z.ndim
0
>>> a = z[()]
>>> b = z[...]
>>> type(a), type(b)
(<class 'numpy.float64'>, <class 'numpy.ndarray'>)
>>> id(z), id(a), id(b)
(4585422896, 4586829384, 4561560080)
>>> id(z.base), id(a.base), id(b.base)
(4557260904, 4557260904, 4585422896)
>>> b.base is z
True
在你给出的例子中,空元组和省略号看起来结果差不多,但它们其实是用来做不同事情的。举个例子,当你在索引一个数组时,A[i, j, k] == A[(i, j, k)]
,而特别地,A[...] == A[(Ellipsis,)]
。这里的元组只是用来装索引元素的。这在你需要把索引当作变量来处理时特别有用,比如你可以这样做:
index = (0,) * A.ndim
A[index]
要注意的是,因为元组是用来装索引元素的,所以它不能和其他索引一起用,比如 A[(), 0] == A[[], 0]
和 A[(), 0] != A[..., 0]
。
因为一个数组 A
可以用比 A.ndim
少的索引来索引,所以用空元组来索引是这种行为的自然延伸,在某些情况下这会很有用,比如上面的代码片段在 A.ndim == 0
时是可以工作的。
简单来说,元组是用来装索引元素的,可以是空的,而省略号则是可能的索引元素之一。
对 A[...]
的处理是一个特殊的情况,经过优化,总是返回 A
本身:
if (op == Py_Ellipsis) {
Py_INCREF(self);
return (PyObject *)self;
}
其他一些看起来应该是等价的情况,比如 A[:]
、A[(Ellipsis,)]
、A[()]
和 A[(slice(None),) * A.ndim]
,实际上会返回 A 的一个“视图”,这个视图的 base
还是 A
:
>>> A[()] is A
False
>>> A[()].base is A
True
这看起来是一种不必要且过早的优化,因为 A[(Ellipsis,)]
和 A[()]
总是会给出相同的结果(即对 A
的完整视图)。从这个链接来看,最开始这样做是因为用 ...
来索引 0维数组时不太正常(在这个更新之前,它会把元素当作标量返回),后来为了保持一致性扩展到了所有数组;自那以后,0维数组的索引问题已经修复,所以这个优化其实不再需要,但它还是以某种方式保留了下来(可能还有一些代码依赖于 A[...] is A
这个判断为真)。