ndarrays
上的索引#
另请参阅
ndarrays
可以使用标准的 Python x[obj]
语法进行索引,其中 x 是数组,obj 是选择对象。根据 obj 的不同,有不同种类的索引可用:基本索引、高级索引和字段访问。
以下大多数示例展示了在数组中引用数据时使用索引。这些示例在赋值给数组时同样有效。有关赋值工作原理的具体示例和解释,请参阅将值赋给索引数组。
请注意,在 Python 中,x[(exp1, exp2, ..., expN)]
等价于 x[exp1, exp2, ..., expN]
;后者只是前者的语法糖。
基本索引#
单个元素索引#
单个元素索引的工作方式与用于其他标准 Python 序列的索引完全相同。它是基于 0 的,并接受负索引从数组的末尾进行索引。
>>> x = np.arange(10)
>>> x[2]
2
>>> x[-2]
8
无需将每个维度的索引分隔成独立的方括号集。
>>> x.shape = (2, 5) # now x is 2-dimensional
>>> x[1, 3]
8
>>> x[1, -1]
9
请注意,如果使用少于维度的索引对多维数组进行索引,则会得到一个子维数组。例如
>>> x[0]
array([0, 1, 2, 3, 4])
也就是说,指定的每个索引都会选择与其余选定维度相对应的数组。在上面的例子中,选择 0 意味着长度为 5 的剩余维度未被指定,并且返回的是具有该维度和大小的数组。必须注意的是,返回的数组是原始数组的视图,即它不是原始数组的副本,而是指向与原始数组相同的内存值。在这种情况下,返回的是第一个位置 (0) 的一维数组。因此,对返回的数组使用单个索引,将返回单个元素。也就是说
>>> x[0][2]
2
因此请注意,x[0, 2] == x[0][2]
,尽管第二种情况效率较低,因为在第一个索引之后创建了一个新的临时数组,然后该临时数组再由 2 进行索引。
注意
NumPy 使用 C 序索引。这意味着最后一个索引通常代表内存中变化最快的位置,这与 Fortran 或 IDL 不同,在 Fortran 或 IDL 中,第一个索引代表内存中变化最快的位置。这种差异可能导致很大的混淆。
切片和步长#
基本切片将 Python 的基本切片概念扩展到 N 维。当 obj 是一个 slice
对象(通过括号内的 start:stop:step
符号构造)、一个整数,或者一个由切片对象和整数组成的元组时,发生基本切片。Ellipsis
和 newaxis
对象也可以与这些对象混合使用。
使用 N 个整数进行索引的最简单情况是返回一个表示相应项的数组标量。与 Python 中一样,所有索引都是基于零的:对于第 i 个索引 \(n_i\),有效范围是 \(0 \le n_i < d_i\),其中 \(d_i\) 是数组形状的第 i 个元素。负索引被解释为从数组末尾开始计数(即,如果 \(n_i < 0\),则表示 \(n_i + d_i\))。
所有通过基本切片生成的数组始终是原始数组的视图。
注意
与内置 Python 序列(如字符串、元组和列表)不同,NumPy 切片会创建一个视图而不是副本。当从一个大数组中提取一小部分,且该小部分在提取后变得无用时,必须小心,因为提取的小部分包含对原始大数组的引用,该原始数组的内存直到所有从其派生出的数组都被垃圾回收后才会释放。在这种情况下,建议进行显式 copy()
操作。
序列切片的标准规则适用于按维度进行的基本切片(包括使用步长索引)。一些需要记住的有用概念包括
基本切片语法是
i:j:k
,其中 i 是起始索引,j 是停止索引,k 是步长(\(k\neq0\))。这会选择 m 个元素(在相应维度中),其索引值为 i, i + k, …, i + (m - 1) k,其中 \(m = q + (r\neq0)\),q 和 r 是 j - i 除以 k 得到的商和余数:j - i = q k + r,从而 i + (m - 1) k < j。例如>>> x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> x[1:7:2] array([1, 3, 5])
负数 i 和 j 被解释为 n + i 和 n + j,其中 n 是相应维度中的元素数量。负数 k 使步长朝向较小的索引。从上面的例子中
>>> x[-2:10] array([8, 9]) >>> x[-3:3:-1] array([7, 6, 5, 4])
假设 n 是正在切片的维度中的元素数量。那么,如果未给出 i,则对于 k > 0 默认为 0,对于 k < 0 默认为 n - 1。如果未给出 j,则对于 k > 0 默认为 n,对于 k < 0 默认为 -n-1。如果未给出 k,则默认为 1。请注意,
::
与:
相同,表示选择沿此轴的所有索引。从上面的例子中>>> x[5:] array([5, 6, 7, 8, 9])
如果选择元组中的对象数量少于 N,则对于任何后续维度,都会假定为
:
。例如>>> x = np.array([[[1],[2],[3]], [[4],[5],[6]]]) >>> x.shape (2, 3, 1) >>> x[1:2] array([[[4], [5], [6]]])
整数 i 返回与
i:i+1
相同的值,**除了**返回对象的维度会减少 1。特别地,选择元组中第 p 个元素为整数(且所有其他条目为:
)时,返回相应的维度为 N - 1 的子数组。如果 N = 1,则返回的对象是数组标量。这些对象在标量中解释。如果选择元组中除了第 p 个条目是切片对象
i:j:k
之外的所有条目都是:
,则返回的数组的维度为 N,该数组是通过沿第 p 轴堆叠由整数索引元素 i, i+k, …, i + (m - 1) k < j 返回的子数组而形成的。切片元组中包含多个非
:
条目的基本切片,其行为类似于使用单个非:
条目重复应用切片,其中非:
条目被依次选取(所有其他非:
条目替换为:
)。因此,x[ind1, ..., ind2,:]
在基本切片下类似于x[ind1][..., ind2, :]
。警告
上述内容对于高级索引**不**适用。
您可以使用切片来设置数组中的值,但(与列表不同)您永远不能增加数组的大小。在
x[obj] = value
中要设置的值的大小必须与x[obj]
的形状相同(或可广播到该形状)。切片元组始终可以构造为 obj 并用于
x[obj]
符号。切片对象可以在构造中代替[start:stop:step]
符号使用。例如,x[1:10:5, ::-1]
也可以实现为obj = (slice(1, 10, 5), slice(None, None, -1)); x[obj]
。这对于构造适用于任意维度数组的通用代码非常有用。有关更多信息,请参阅在程序中处理可变数量的索引。
维度索引工具#
有一些工具可以方便地将数组形状与表达式和赋值进行匹配。
Ellipsis
会展开为选择元组中索引所有维度所需的 :
对象的数量。在大多数情况下,这意味着展开的选择元组的长度是 x.ndim
。只能存在一个省略号。从上面的例子中
>>> x[..., 0]
array([[1, 2, 3],
[4, 5, 6]])
这等价于
>>> x[:, :, 0]
array([[1, 2, 3],
[4, 5, 6]])
选择元组中的每个 newaxis
对象都用于将结果选择的维度扩展一个单位长度维度。添加的维度是 newaxis
对象在选择元组中的位置。newaxis
是 None
的别名,None
可以代替它使用并得到相同的结果。从上面的例子中
>>> x[:, np.newaxis, :, :].shape
(2, 1, 3, 1)
>>> x[:, None, :, :].shape
(2, 1, 3, 1)
这对于以一种否则需要显式重塑操作的方式组合两个数组非常方便。例如
>>> x = np.arange(5)
>>> x[:, np.newaxis] + x[np.newaxis, :]
array([[0, 1, 2, 3, 4],
[1, 2, 3, 4, 5],
[2, 3, 4, 5, 6],
[3, 4, 5, 6, 7],
[4, 5, 6, 7, 8]])
高级索引#
当选择对象 obj 是非元组序列对象、ndarray
(数据类型为整数或布尔),或者包含至少一个序列对象或 ndarray(数据类型为整数或布尔)的元组时,会触发高级索引。高级索引有两种类型:整数索引和布尔索引。
高级索引总是返回数据的副本(与返回视图的基本切片形成对比)。
警告
高级索引的定义意味着 x[(1, 2, 3),]
与 x[(1, 2, 3)]
根本不同。后者等价于 x[1, 2, 3]
,这将触发基本选择,而前者将触发高级索引。请务必理解为何会发生这种情况。
整数数组索引#
整数数组索引允许根据其 N 维索引选择数组中的任意项。每个整数数组代表该维度中的多个索引。
索引数组中允许负值,其工作方式与单个索引或切片相同
>>> x = np.arange(10, 1, -1)
>>> x
array([10, 9, 8, 7, 6, 5, 4, 3, 2])
>>> x[np.array([3, 3, 1, 8])]
array([7, 7, 9, 2])
>>> x[np.array([3, 3, -3, 8])]
array([7, 7, 4, 2])
如果索引值超出范围,则会抛出 IndexError
>>> x = np.array([[1, 2], [3, 4], [5, 6]])
>>> x[np.array([1, -1])]
array([[3, 4],
[5, 6]])
>>> x[np.array([3, 4])]
Traceback (most recent call last):
...
IndexError: index 3 is out of bounds for axis 0 with size 3
当索引由与被索引数组的维度数量相同的整数数组组成时,索引是直接的,但与切片不同。
高级索引总是被广播并作为一个进行迭代
result[i_1, ..., i_M] == x[ind_1[i_1, ..., i_M], ind_2[i_1, ..., i_M],
..., ind_N[i_1, ..., i_M]]
请注意,结果形状与(广播后的)索引数组形状 ind_1, ..., ind_N
相同。如果索引无法广播到相同形状,则会引发异常 IndexError: shape mismatch: indexing arrays could not be broadcast together with shapes...
。
使用多维索引数组进行索引通常是较不寻常的用法,但它们是允许的,并且对于某些问题很有用。我们将从最简单的多维情况开始
>>> y = np.arange(35).reshape(5, 7)
>>> y
array([[ 0, 1, 2, 3, 4, 5, 6],
[ 7, 8, 9, 10, 11, 12, 13],
[14, 15, 16, 17, 18, 19, 20],
[21, 22, 23, 24, 25, 26, 27],
[28, 29, 30, 31, 32, 33, 34]])
>>> y[np.array([0, 2, 4]), np.array([0, 1, 2])]
array([ 0, 15, 30])
在这种情况下,如果索引数组具有匹配的形状,并且被索引数组的每个维度都有一个索引数组,则结果数组的形状与索引数组相同,并且值对应于索引数组中每个位置的索引集。在此示例中,两个索引数组的第一个索引值都为 0,因此结果数组的第一个值为 y[0, 0]
。下一个值为 y[2, 1]
,最后一个为 y[4, 2]
。
如果索引数组的形状不同,会尝试将它们广播到相同形状。如果它们无法广播到相同形状,则会引发异常
>>> y[np.array([0, 2, 4]), np.array([0, 1])]
Traceback (most recent call last):
...
IndexError: shape mismatch: indexing arrays could not be broadcast
together with shapes (3,) (2,)
广播机制允许将索引数组与其他索引的标量结合。其效果是标量值用于索引数组的所有相应值
>>> y[np.array([0, 2, 4]), 1]
array([ 1, 15, 29])
跳到下一个复杂级别,可以仅使用索引数组部分索引数组。理解在这种情况下会发生什么需要一些思考。例如,如果我们只使用一个索引数组与 y
>>> y[np.array([0, 2, 4])]
array([[ 0, 1, 2, 3, 4, 5, 6],
[14, 15, 16, 17, 18, 19, 20],
[28, 29, 30, 31, 32, 33, 34]])
这导致构造了一个新数组,其中索引数组的每个值从被索引数组中选择一行,结果数组的形状为(索引元素数量,行大小)。
通常,结果数组的形状将是索引数组的形状(或所有索引数组广播到的形状)与被索引数组中任何未使用维度(未被索引的维度)的形状的串联。
示例
从每一行中,应选择一个特定元素。行索引只是 [0, 1, 2]
,列索引指定了要为相应行选择的元素,这里是 [0, 1, 0]
。将两者结合起来,可以使用高级索引解决此任务
>>> x = np.array([[1, 2], [3, 4], [5, 6]])
>>> x[[0, 1, 2], [0, 1, 0]]
array([1, 4, 5])
为了实现类似于上述基本切片的行为,可以使用广播。函数 ix_
可以帮助实现此广播。这最好通过一个示例来理解。
示例
从一个 4x3 数组中,应使用高级索引选择角元素。因此,需要选择列为 [0, 2]
之一且行为 [0, 3]
之一的所有元素。要使用高级索引,需要显式地选择所有元素。使用之前解释的方法,可以写出
>>> x = np.array([[ 0, 1, 2],
... [ 3, 4, 5],
... [ 6, 7, 8],
... [ 9, 10, 11]])
>>> rows = np.array([[0, 0],
... [3, 3]], dtype=np.intp)
>>> columns = np.array([[0, 2],
... [0, 2]], dtype=np.intp)
>>> x[rows, columns]
array([[ 0, 2],
[ 9, 11]])
然而,由于上面的索引数组只是重复自身,因此可以使用广播(比较诸如 rows[:, np.newaxis] + columns
之类的操作)来简化这一点
>>> rows = np.array([0, 3], dtype=np.intp)
>>> columns = np.array([0, 2], dtype=np.intp)
>>> rows[:, np.newaxis]
array([[0],
[3]])
>>> x[rows[:, np.newaxis], columns]
array([[ 0, 2],
[ 9, 11]])
这种广播也可以使用函数 ix_
来实现
>>> x[np.ix_(rows, columns)]
array([[ 0, 2],
[ 9, 11]])
请注意,如果没有调用 np.ix_
,将只选择对角线元素
>>> x[rows, columns]
array([ 0, 11])
这种差异是关于使用多个高级索引进行索引时最重要的一点。
示例
高级索引可能有用的一种实际示例是颜色查找表,我们希望将图像的值映射到 RGB 三元组以进行显示。查找表可以具有形状 (nlookup, 3)。使用形状为 (ny, nx) 且 dtype=np.uint8(或任何整数类型,只要值在查找表的范围内)的图像索引此类数组,将生成形状为 (ny, nx, 3) 的数组,其中 RGB 三元组与每个像素位置相关联。
布尔数组索引#
这种高级索引发生在 obj 是布尔类型的数组对象时,例如可能从比较运算符返回的对象。单个布尔索引数组实际上与 x[obj.nonzero()]
相同,如上所述,obj.nonzero()
返回一个元组(长度为 obj.ndim
)的整数索引数组,显示 obj 中 True
元素的位置。然而,当 obj.shape == x.shape
时,它更快。
如果 obj.ndim == x.ndim
,则 x[obj]
返回一个一维数组,其中填充了 x 中与 obj 的 True
值对应的元素。搜索顺序将是行优先的 C 风格。如果 obj 的形状与 x 的相应维度不匹配,则会引发索引错误,无论这些值是 True
还是 False
。
一个常见的用例是筛选所需的元素值。例如,可能希望从数组中选择所有非 numpy.nan
的条目
>>> x = np.array([[1., 2.], [np.nan, 3.], [np.nan, np.nan]])
>>> x[~np.isnan(x)]
array([1., 2., 3.])
或者希望将一个常数添加到所有负元素中
>>> x = np.array([1., -1., -2., 3])
>>> x[x < 0] += 20
>>> x
array([ 1., 19., 18., 3.])
通常,如果索引包含布尔数组,结果将与在相同位置插入 obj.nonzero()
并使用上述整数数组索引机制的结果相同。x[ind_1, boolean_array, ind_2]
等价于 x[(ind_1,) + boolean_array.nonzero() + (ind_2,)]
。
如果只有一个布尔数组并且没有整数索引数组,这是很简单的。只需要确保布尔索引的维度与它应该处理的维度完全相同即可。
通常,当布尔数组的维度少于被索引数组时,这等价于 x[b, ...]
,这意味着 x 首先由 b 索引,然后跟着所需数量的 :
以填充 x 的秩。因此,结果的形状是包含布尔数组中 True 元素数量的一个维度,后面跟着被索引数组的剩余维度
>>> x = np.arange(35).reshape(5, 7)
>>> b = x > 20
>>> b[:, 5]
array([False, False, False, True, True])
>>> x[b[:, 5]]
array([[21, 22, 23, 24, 25, 26, 27],
[28, 29, 30, 31, 32, 33, 34]])
这里从被索引数组中选择第4和第5行并组合成一个二维数组。
示例
从数组中选择所有和小于或等于二的行
>>> x = np.array([[0, 1], [1, 1], [2, 2]])
>>> rowsum = x.sum(-1)
>>> x[rowsum <= 2, :]
array([[0, 1],
[1, 1]])
结合多个布尔索引数组或将布尔数组与整数索引数组结合,最好通过 obj.nonzero()
类比来理解。函数 ix_
也支持布尔数组,并且会正常工作,不会有任何意外。
示例
使用布尔索引选择所有总和为偶数的行。同时,应使用高级整数索引选择第 0 列和第 2 列。使用 ix_
函数可以完成此操作
>>> x = np.array([[ 0, 1, 2],
... [ 3, 4, 5],
... [ 6, 7, 8],
... [ 9, 10, 11]])
>>> rows = (x.sum(-1) % 2) == 0
>>> rows
array([False, True, False, True])
>>> columns = [0, 2]
>>> x[np.ix_(rows, columns)]
array([[ 3, 5],
[ 9, 11]])
如果没有调用 np.ix_
,将只选择对角线元素。
或者不使用 np.ix_
(比较整数数组示例)
>>> rows = rows.nonzero()[0]
>>> x[rows[:, np.newaxis], columns]
array([[ 3, 5],
[ 9, 11]])
示例
使用形状为 (2, 3) 包含四个 True 元素的二维布尔数组从形状为 (2, 3, 5) 的三维数组中选择行,结果将是形状为 (4, 5) 的二维数组
>>> x = np.arange(30).reshape(2, 3, 5)
>>> x
array([[[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]],
[[15, 16, 17, 18, 19],
[20, 21, 22, 23, 24],
[25, 26, 27, 28, 29]]])
>>> b = np.array([[True, True, False], [False, True, True]])
>>> x[b]
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[20, 21, 22, 23, 24],
[25, 26, 27, 28, 29]])
结合高级索引和基本索引#
当索引中至少有一个切片 (:
)、省略号 (...
) 或 newaxis
(或者数组的维度多于高级索引的数量)时,行为会更复杂。这就像将每个高级索引元素的索引结果进行串联。
在最简单的情况下,只有一个单独的高级索引与一个切片结合。例如
>>> y = np.arange(35).reshape(5,7)
>>> y[np.array([0, 2, 4]), 1:3]
array([[ 1, 2],
[15, 16],
[29, 30]])
实际上,切片和索引数组操作是独立的。切片操作提取索引为 1 和 2 的列(即第 2 和第 3 列),然后是索引数组操作,该操作提取索引为 0、2 和 4 的行(即第 1、第 3 和第 5 行)。这等价于
>>> y[:, 1:3][np.array([0, 2, 4]), :]
array([[ 1, 2],
[15, 16],
[29, 30]])
例如,单个高级索引可以替换切片,并且结果数组将是相同的。然而,它是一个副本,并且可能具有不同的内存布局。如果可能,切片是更优的选择。例如
>>> x = np.array([[ 0, 1, 2],
... [ 3, 4, 5],
... [ 6, 7, 8],
... [ 9, 10, 11]])
>>> x[1:2, 1:3]
array([[4, 5]])
>>> x[1:2, [1, 2]]
array([[4, 5]])
理解多个高级索引组合的最简单方法可能是从结果形状的角度来思考。索引操作分为两部分:由基本索引(不包括整数)定义的子空间和高级索引部分定义的子空间。需要区分两种索引组合情况
所有高级索引都彼此相邻。例如
x[..., arr1, arr2, :]
但**不是**x[arr1, :, 1]
,因为1
在这方面是一个高级索引。
在第一种情况下,高级索引操作产生的维度在结果数组中排在前面,子空间维度排在后面。在第二种情况下,高级索引操作产生的维度被插入到结果数组中与它们在初始数组中相同的位置(后一种逻辑使得简单的高级索引行为与切片完全相同)。
示例
假设 x.shape
是 (10, 20, 30),而 ind
是一个 (2, 5, 2) 形状的索引 intp
数组,那么 result = x[..., ind, :]
的形状是 (10, 2, 5, 2, 30),因为 (20,) 形状的子空间已被 (2, 5, 2) 形状的广播索引子空间替换。如果我们让 i, j, k 遍历 (2, 5, 2) 形状的子空间,那么 result[..., i, j, k, :] = x[..., ind[i, j, k], :]
。这个例子产生的结果与 x.take(ind, axis=-2)
相同。
示例
假设 x.shape
为 (10, 20, 30, 40, 50),并且 ind_1
和 ind_2
可以广播到形状 (2, 3, 4)。那么 x[:, ind_1, ind_2]
的形状为 (10, 2, 3, 4, 40, 50),因为 X 的 (20, 30) 形状子空间已被索引的 (2, 3, 4) 子空间替换。然而,x[:, ind_1, :, ind_2]
的形状为 (2, 3, 4, 10, 30, 50),因为没有明确的位置来放置索引子空间,因此它被附加到开头。始终可以使用 .transpose()
将子空间移动到任何需要的位置。请注意,此示例无法使用 take
复制。
示例
切片可以与广播的布尔索引结合使用
>>> x = np.arange(35).reshape(5, 7)
>>> b = x > 20
>>> b
array([[False, False, False, False, False, False, False],
[False, False, False, False, False, False, False],
[False, False, False, False, False, False, False],
[ True, True, True, True, True, True, True],
[ True, True, True, True, True, True, True]])
>>> x[b[:, 5], 1:3]
array([[22, 23],
[29, 30]])
字段访问#
另请参阅
如果 ndarray
对象是一个结构化数组,则可以通过使用字符串(类似字典的方式)对数组进行索引来访问数组的字段。
索引 x['field-name']
返回数组的一个新视图,其形状与 x 相同(除非字段是子数组),但数据类型为 x.dtype['field-name']
,并且只包含指定字段中的数据部分。此外,记录数组标量也可以通过这种方式“索引”。
对结构化数组进行索引也可以使用字段名称列表,例如 x[['field-name1', 'field-name2']]
。从 NumPy 1.16 版开始,这会返回一个只包含这些字段的视图。在旧版本的 NumPy 中,它会返回一个副本。有关多字段索引的更多信息,请参阅用户指南中关于结构化数组的部分。
如果访问的字段是子数组,则子数组的维度会附加到结果的形状中。例如
>>> x = np.zeros((2, 2), dtype=[('a', np.int32), ('b', np.float64, (3, 3))])
>>> x['a'].shape
(2, 2)
>>> x['a'].dtype
dtype('int32')
>>> x['b'].shape
(2, 2, 3, 3)
>>> x['b'].dtype
dtype('float64')
扁平迭代器索引#
x.flat
返回一个迭代器,它将遍历整个数组(以 C 连续方式,最后一个索引变化最快)。只要选择对象不是元组,此迭代器对象也可以使用基本切片或高级索引进行索引。这应该从 x.flat
是一个一维视图这一事实中显而易见。它可用于使用一维 C 风格扁平索引进行整数索引。因此,任何返回数组的形状都是整数索引对象的形状。
将值赋给索引数组#
如前所述,可以使用单个索引、切片以及索引和掩码数组来选择数组的子集进行赋值。要赋值给索引数组的值必须形状一致(与索引产生的形状相同或可广播到该形状)。例如,允许将常量赋给切片
>>> x = np.arange(10)
>>> x[2:7] = 1
或一个正确大小的数组
>>> x[2:7] = np.arange(5)
请注意,如果将较高类型的值赋给较低类型(如将浮点数赋给整数),赋值可能会导致更改,甚至引发异常(将复数赋给浮点数或整数)
>>> x[1] = 1.2
>>> x[1]
1
>>> x[1] = 1.2j
Traceback (most recent call last):
...
TypeError: can't convert complex to int
与某些引用(例如数组和掩码索引)不同,赋值始终是对数组中的原始数据进行的(事实上,否则也毫无意义!)。但请注意,某些操作可能不会像人们天真地期望的那样工作。这个特殊的例子常常令人惊讶
>>> x = np.arange(0, 50, 10)
>>> x
array([ 0, 10, 20, 30, 40])
>>> x[np.array([1, 1, 3, 1])] += 1
>>> x
array([ 0, 11, 20, 31, 40])
人们期望第一个位置会增加 3。实际上,它只会增加 1。原因是,从原始数组中提取一个新数组(作为临时数组),其中包含位置 1、1、3、1 的值,然后将值 1 添加到临时数组中,最后将临时数组重新赋值给原始数组。因此,数组中 x[1] + 1
的值被三次赋给 x[1]
,而不是增加 3 次。
在程序中处理可变数量的索引#
索引语法非常强大,但在处理可变数量的索引时存在局限性。例如,如果您想编写一个可以处理具有各种维度数量的参数的函数,而无需为每种可能的维度数量编写特殊代码,那该怎么做呢?如果给索引提供一个元组,该元组将被解释为索引列表。例如
>>> z = np.arange(81).reshape(3, 3, 3, 3)
>>> indices = (1, 1, 1, 1)
>>> z[indices]
40
因此,可以使用代码构造任意数量索引的元组,然后将其用作索引。
切片可以在程序中使用 Python 中的 slice()
函数来指定。例如
>>> indices = (1, 1, 1, slice(0, 2)) # same as [1, 1, 1, 0:2]
>>> z[indices]
array([39, 40])
同样,省略号可以通过使用 Ellipsis 对象在代码中指定
>>> indices = (1, Ellipsis, 1) # same as [1, ..., 1]
>>> z[indices]
array([[28, 31, 34],
[37, 40, 43],
[46, 49, 52]])
因此,可以直接使用 np.nonzero()
函数的输出作为索引,因为它总是返回一个索引数组的元组。
由于对元组的特殊处理,它们不会像列表那样自动转换为数组。例如
>>> z[[1, 1, 1, 1]] # produces a large array
array([[[[27, 28, 29],
[30, 31, 32], ...
>>> z[(1, 1, 1, 1)] # returns a single value
40
详细说明#
这些是一些详细说明,对于日常索引并不重要(无特定顺序)
NumPy 本身的索引类型是
intp
,它可能与默认整数数组类型不同。intp
是足以安全索引任何数组的最小数据类型;对于高级索引,它可能比其他类型更快。对于高级赋值,通常不保证迭代顺序。这意味着如果一个元素被设置多次,则无法预测最终结果。
一个空的(元组)索引是一个指向零维数组的完整标量索引。
x[()]
如果x
是零维的,则返回一个标量,否则返回一个视图。另一方面,x[...]
总是返回一个视图。如果索引中存在一个零维数组,**并且**它是一个完整的整数索引,则结果将是一个标量而不是零维数组。(高级索引未被触发。)
当存在省略号 (
...
) 但其没有大小(即替换零个:
)时,结果仍将始终是一个数组。如果没有高级索引,则为视图;否则为副本。布尔数组的
nonzero
等效性不适用于零维布尔数组。当高级索引操作的结果没有元素,但单个索引超出边界时,是否引发
IndexError
是未定义的(例如x[[], [123]]
,其中123
超出边界)。在赋值过程中发生类型转换错误时(例如使用字符串序列更新数值数组),被赋值的数组可能会处于不可预测的部分更新状态。但是,如果发生任何其他错误(例如索引越界),数组将保持不变。
高级索引结果的内存布局针对每个索引操作进行了优化,因此无法假定特定的内存顺序。
当使用子类(特别是操作其形状的子类)时,默认的
ndarray.__setitem__
行为将为基本索引调用__getitem__
,但不适用于高级索引。对于此类子类,可能更优的做法是使用数据上的基类 ndarray 视图来调用ndarray.__setitem__
。如果子类的__getitem__
不返回视图,则必须这样做。