使用一个虚拟类,我可以确定解释器如何将[...]
转换为对__getitem__
的调用。
In [1073]: class Foo():
...: def __getitem__(idx):
...: print(idx)
In [1080]: Foo()[1,2,slice(None)]
(1, 2, slice(None, None, None))
In [1081]: Foo()[(1,2,slice(None))]
(1, 2, slice(None, None, None))
In [1082]: Foo()[[1,2,slice(None)]]
[1, 2, slice(None, None, None)]
使用()将多个术语包装起来没有任何区别——在两种情况下都会得到一个元组。而列表作为列表传递。
因此,元组和列表(或不是)之间的区别必须在numpy
源代码中编码——这是编译的。因此我不能轻易地研究它。
对于一维数组
使用列表进行索引会产生高级索引——选择特定的值:
In [1085]: arr[[1,2,3]]
Out[1085]: array([ 0.73703368, 0. , 0. ])
但是,如果将其中一个值替换为元组或切片:
In [1086]: arr[[1,2,(2,3)]]
IndexError: too many indices for array
In [1088]: arr[[1,2,slice(None)]]
IndexError: too many indices for array
列表被视为元组-它尝试将值与维度进行匹配。
因此,在顶层上,列表和元组被视为相同的 - 如果列表不能解释为高级索引列表。
还要注意单项列表之间的差异。
In [1089]: arr[[1]]
Out[1089]: array([ 0.73703368])
In [1090]: arr[(1,)]
Out[1090]: 0.73703367969998546
In [1091]: arr[1]
Out[1091]: 0.73703367969998546
一些函数(例如
np.apply_along/over_axis
)会生成索引列表或数组,然后对其进行操作。它们使用列表或数组是因为它们是可变的。有些函数在使用索引前会将其包装成元组,而有些则没有。这种差异让我有点困扰,但这些测试用例表明,这种包装成元组的做法通常是可选的。
In [1092]: idx=[1,2,slice(None)]
In [1093]: np.ones((2,3,4))[idx]
Out[1093]: array([ 1., 1., 1., 1.])
In [1094]: np.ones((2,3,4))[tuple(idx)]
Out[1094]: array([ 1., 1., 1., 1.])
如果我将索引构建为对象数组,似乎仍然需要元组包装器:
In [1096]: np.ones((2,3,4))[np.array(idx)]
...
IndexError: arrays used as indices must be of integer (or boolean) type
In [1097]: np.ones((2,3,4))[tuple(np.array(idx))]
Out[1097]: array([ 1., 1., 1., 1.])
===================
来自函数@Eric
的评论
===================
此函数将对象数组和列表包装成元组以进行索引:
def apply_along_axis(func1d, axis, arr, *args, **kwargs):
....
ind = [0]*(nd-1)
i = zeros(nd, 'O')
....
res = func1d(arr[tuple(i.tolist())], *args, **kwargs)
outarr[tuple(ind)] = res
更新
现在,此列表索引会产生一个FutureWarning
警告:
In [113]: arr.shape
Out[113]: (2, 3, 4)
In [114]: arr[[1, 2, slice(None)]]
<ipython-input-114-f30c20184e42>:1: FutureWarning: Using a non-tuple sequence for multidimensional indexing is deprecated; use `arr[tuple(seq)]` instead of `arr[seq]`. In the future this will be interpreted as an array index, `arr[np.array(seq)]`, which will result either in an error or a different result.
arr[[1, 2, slice(None)]]
Out[114]: array([20, 21, 22, 23])
将列表改为元组可以得到相同的结果,而且不会出现警告信息:
In [115]: arr[(1, 2, slice(None))]
Out[115]: array([20, 21, 22, 23])
这和以下内容是一样的:
In [116]: arr[1, 2, :]
Out[116]: array([20, 21, 22, 23])
使用逗号进行索引会创建一个元组,该元组将传递给__setitem__
方法。
警告说,在未来它将尝试将列表转换为数组而不是元组:
In [117]: arr[np.array([1, 2, slice(None)])]
Traceback (most recent call last):
Input In [117] in <module>
arr[np.array([1, 2, slice(None)])]
IndexError: arrays used as indices must be of integer (or boolean) type
但使用slice
对象会引发错误。因此,arr[tuple([....])]
的解释是唯一有意义的。但这是一个遗留问题,来自早期的numeric
包。
幸运的是,新手程序员不太可能尝试这样做。他们可能会尝试arr[[1,2,:]]
,但那会导致语法错误。冒号:
只允许在索引括号中使用,而不允许在列表括号(或元组())中使用。
这些评论的当前轮次是由一个不同的情况触发的,该情况产生了FutureWarning:
In [123]: arr[[[0, 1], [1, 0]]]
<ipython-input-123-4fa43c8569dd>:1: FutureWarning: Using a non-tuple sequence for multidimensional indexing is deprecated
arr[[[0, 1], [1, 0]]]
Out[123]:
array([[ 4, 5, 6, 7],
[12, 13, 14, 15]])
这里嵌套列表被解释为一个包含列表的元组,甚至可以是:
In [124]: arr[[0, 1], [1, 0]]
Out[124]:
array([[ 4, 5, 6, 7],
[12, 13, 14, 15]])
In [126]: arr[np.array([[0, 1], [1, 0]])].shape
Out[126]: (2, 2, 3, 4)
虽然出现了同样的警告,但遗留代码为什么选择采用元组解释并不十分明显,我没有看到相关文档。