ndarray 上的ndarray 索引#

另请参阅

索引例程

ndarray 可以使用标准的 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)的 1-D 数组。因此,使用单个索引来索引返回的数组,结果是返回一个单元素。即:

>>> x[0][2]
2

因此,请注意 x[0, 2] == x[0][2],尽管第二种情况效率较低,因为在第一个索引后会创建一个新的临时数组,然后该临时数组再被索引 2。

注意

NumPy 使用 C 语言顺序索引。这意味着最后一个索引通常表示最快变化的内存位置,这与 Fortran 或 IDL 不同,在 Fortran 或 IDL 中,第一个索引表示内存中最快变化的内存位置。这种差异可能导致极大的困惑。

切片和步进#

基本切片将 Python 的基本切片概念扩展到 N 维。当 *obj* 是一个 slice 对象(通过方括号中的 start:stop:step 表示法构造)、一个整数或一个切片对象和整数的元组时,就会发生基本切片。Ellipsisnewaxis 对象也可以与这些对象交错使用。

使用 *N* 个整数进行索引的最简单情况返回一个数组标量,表示相应的项。与 Python 一样,所有索引都是从 0 开始的:对于第 *i* 个索引 \(n_i\),有效范围是 \(0 \le n_i < d_i\),其中 \(d_i\) 是数组形状的第 *i* 个元素。负数索引被解释为从数组末尾计数(即,如果 \(n_i < 0\),则表示 \(n_i + d_i\)。)。

基本切片生成的所有数组始终是原始数组的视图

注意

NumPy 切片创建的是视图,而不是像内置 Python 序列(如字符串、元组和列表)那样创建副本。在从大型数组中提取一小部分时必须小心,因为提取出的那一小部分包含对大型原始数组的引用,而该大型原始数组的内存直到所有派生自它的数组都被垃圾回收后才会释放。在这种情况下,建议显式调用 copy()

标准序列切片规则适用于逐个维度的基本切片(包括使用步进索引)。以下是一些有用的概念,值得记住:

  • 基本切片语法是 i:j:k,其中 *i* 是起始索引,*j* 是停止索引,*k* 是步长(\(k\neq0\)。)。这会选择索引值为 *i*, *i + k*, ..., *i + (m - 1) k* 的 *m* 个元素(在相应维度中),其中 \(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 对象的位置。newaxisNone 的别名,并且 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 行,并将它们组合成一个 2-D 数组。

示例

从一个数组中,选择所有总和小于或等于二的行:

>>> 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-D 布尔数组来选择形状为 (2, 3, 5) 的 3-D 数组的行,结果是一个形状为 (4, 5) 的 2-D 结果。

>>> 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]])

理解*多个*高级索引的组合的最简单方法可能是从结果形状的角度来考虑。索引操作有两个部分:由基本索引(不包括整数)定义的子空间和来自高级索引部分(包括整数)的子空间。需要区分两种索引组合的情况:

  • 高级索引由切片、Ellipsisnewaxis 分隔。例如 x[arr1, :, arr2]

  • 高级索引紧挨在一起。例如 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_1ind_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

与某些引用(如数组和掩码索引)不同,赋值始终是对数组中的原始数据进行的(事实上,没有其他方式才有意义!)。但请注意,某些操作可能不如人们 naively 预期的那样工作。这个特定的示例常常会让人感到惊讶

>>> 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__ 不返回视图,则必须这样做。