Имеет ли назначение с расширенными индексами данных массива индексирования?

Я медленно пытаюсь понять разницу между view и copy в numpy, а также mutable vs. immutable types.

Если я обращаюсь к части массива с 'advanced indexing', он должен вернуть копию. Это выглядит так:

In [1]: import numpy as np
In [2]: a = np.zeros((3,3))
In [3]: b = np.array(np.identity(3), dtype=bool)

In [4]: c = a[b]

In [5]: c[:] = 9

In [6]: a
Out[6]: 
array([[ 0.,  0.,  0.],
       [ 0.,  0.,  0.],
       [ 0.,  0.,  0.]])

Так как c - это просто копия, он не передает данные, а изменение не мутирует a. Однако это меня смущает:

In [7]: a[b] = 1

In [8]: a
Out[8]: 
array([[ 1.,  0.,  0.],
       [ 0.,  1.,  0.],
       [ 0.,  0.,  1.]])

Итак, кажется, даже если я использую расширенную индексацию, назначение по-прежнему обрабатывает вещь слева как представление. Очевидно, что строка a в строке 2 - это тот же объект/данные, что и a в строке 6, поскольку мутация c не влияет на нее.

Итак, мой вопрос: есть ли a в строке 8 тот же объект/данные, что и раньше (не считая диагонали, конечно), или это копия? Другими словами, были ли данные a скопированы в новый a или были ли эти данные мутированы на месте?

Например, это похоже на:

x = [1,2,3]
x += [4]

или как:

y = (1,2,3)
y += (4,)

Я не знаю, как это проверить, потому что в любом случае a.flags.owndata есть True. Пожалуйста, не стесняйтесь разрабатывать или отвечать на другой вопрос, если я думаю об этом в замешательстве.

Ответ 1

Когда вы выполняете c = a[b], a.__get_item__ вызывается с b в качестве единственного аргумента, и все, что возвращается, присваивается c.

Когда вы выполняете a[b] = c, a.__setitem__ вызывается с b и c в качестве аргументов, и все, что возвращается, тихо отбрасывается.

Поэтому, несмотря на наличие синтаксиса a[b], оба выражения делают разные вещи. Вы можете подклассифицировать ndarray, перегрузить эти две функции и заставить их вести себя по-другому. Как по умолчанию в numpy, первый возвращает копию (если b - массив), но последний изменяет a на месте.

Ответ 2

Да, это тот же объект. Вот как вы проверяете:

>>> a
array([[ 0.,  0.,  0.],
       [ 0.,  0.,  0.],
       [ 0.,  0.,  0.]])
>>> a2 = a
>>> a[b] = 1
>>> a2 is a
True
>>> a2
array([[ 1.,  0.,  0.],
       [ 0.,  1.,  0.],
       [ 0.,  0.,  1.]])

Назначение некоторому выражению в Python - это не то же самое, что просто чтение значения этого выражения. Когда вы делаете c = a[b], с a[b] справа от знака равенства, он возвращает новый объект. Когда вы делаете a[b] = 1, с a[b] слева от знака равенства, он изменяет исходный объект.

На самом деле выражение типа a[b] = 1 не может изменить то, к чему привязано имя a. Код, обрабатывающий obj[index] = value, только узнает объект obj, а не то, какое имя было использовано для ссылки на этот объект, поэтому он не может изменить то, на что ссылается это имя.

Ответ 3

Похоже, это распространенное недоразумение, цитата из официального документа: (https://scipy-cookbook.readthedocs.io/items/ViewsVsCopies.html)

Эмпирическое правило здесь может быть следующим: в контексте индексации lvalue (т.е. Индексы размещаются в левом значении присваивания), представление или копия массива не создаются (поскольку в этом нет необходимости). Однако с обычными значениями вышеупомянутые правила для создания представлений действительно применяются.

Другими словами, понятие view или copy относится только к ситуации извлечения значений из numpy объекта.