В numpy, что делает индексирование массива с пустым кортежем против эллипсиса?
Я просто обнаружил - случайно - что массив в numpy
может быть проиндексирован пустым кортежем:
In [62]: a = arange(5)
In [63]: a[()]
Out[63]: array([0, 1, 2, 3, 4])
Я нашел некоторую документацию по NumPy вики ZeroRankArray:
(Саша) Во-первых, какой бы выбор ни был сделан для x [...] и x [()], они должны быть одинаковыми, потому что... это просто синтаксический сахар для "столько, сколько нужно", что в случае нуля ранг ведет к... = (:,) * 0 =(). Во-вторых, массивы с нулевым рангом и скалярные типы numpy взаимозаменяемы внутри numpy, но скалярные скаляры могут использоваться в некоторых конструкциях python, где ndarrays не могут.
Таким образом, для 0-d массивов 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
s. Индексация списками документируется в индексных массивах, и в конце того же документа есть краткое уведомление об индексации кортежами.
Это оставляет вопрос:
Разница между a[()]
и a[...]
дизайном? Каков дизайн, тогда?
(Вопрос чем-то напоминает: что делает пустой '()' на матрице Matlab?)
Редактировать:
Фактически, даже скаляры могут быть проиндексированы пустым кортежем:
In [36]: numpy.int64(10)[()]
Out[36]: 10
Ответы
Ответ 1
Обработка 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
). От взгляда на https://github.com/numpy/numpy/commit/fa547b80f7035da85f66f9cbabc4ff75969d23cd кажется, что это было изначально необходимо, потому что индексация с помощью ...
не работала должным образом на 0d массивах (ранее до https://github.com/numpy/numpy/commit/4156b241aa3670f923428d4e72577a9962cdf042 он вернет элемент как скаляр), а затем расширен до всех массивов для согласованности; с тех пор индексирование было зафиксировано на 0d массивах, поэтому оптимизация не требуется, но ему удалось провести рутинный (и, вероятно, некоторый код, который зависит от A[...] is A
, является истинным).
Ответ 2
Пока в примере, который вы указали, пустые кортежи и многоточие дают аналогичный результат, в целом они служат различным целям. При индексировании массива 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
, индексирование с пустым кортежем является естественным расширением этого поведения, и оно может быть полезно в некоторых ситуациях, например, приведенный выше код snipit будет работать, когда A.ndim == 0
.
Короче говоря, кортеж служит контейнером для индексирования элементов, которому разрешено быть пустым, в то время как Эллипсис является одним из возможных элементов индексации.
Ответ 3
Согласно официальной документации Numpy, различия очевидны:
Пустой (кортежный) индекс - это полный скалярный индекс в массиве нулевого измерения. x[()]
возвращает скаляр, если x
является нуль-мерным, и представление в противном случае. С другой стороны, x[...]
всегда возвращает представление.
Когда многоточие (...
) присутствует, но не имеет размера (т.е. заменяет ноль :
) результат все равно будет всегда быть массивом. Представление, если расширенного индекса нет, в противном случае - копия.
>>> import numpy as np
>>> 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)
>>> # ---------- #
>>> 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