Нашли ошибку?
среднийNumPy 2.5.3

Представления и копии

Когда изменение «нового» массива меняет исходный — и как это проверить.

Нужно знать:Индексация и срезыFancy-индексация

Содержание статьи

Коротко

  • Представление (view) — новый объект-окно на те же данные. Изменения видны в обоих массивах.
  • Копия (copy) — отдельные данные; изменения независимы.
  • Представления дают: срезы a[1:4], a[::2], reshape, ravel (если возможно), .T, transpose.
  • Копии дают: fancy- и булева индексация (a[[0, 2]], a[a > 0]), арифметика, flatten, .copy().
  • Проверка: b.base is a — представление a; np.shares_memory(a, b) — есть ли общие данные.

Синтаксис

b = a[1:4]                 # представление
b = a[1:4].copy()          # копия
b.base is a                # True — b смотрит в данные a
b.base is None             # True — у b свои данные
np.shares_memory(a, b)
Операция Результат
срез a[i:j], a[::k], a[:, 0] представление
reshape, .T, transpose, ravel обычно представление
a[[0, 2]], a[mask] копия
a + 1, np.sqrt(a), flatten(), copy() копия (новый массив)

Примеры

Данные для примеровиспользуются во всех примерах ниже
prices = np.array([100, 200, 300, 400, 500])
Срез меняет исходный массив
part = prices[1:4]          # срез — представление
part[0] = 0
prices                      # исходный массив изменился
Вывод
array([100,   0, 300, 400, 500])
Копия не меняет
part = prices[1:4].copy()   # независимая копия
part[0] = 0
prices
Вывод
array([100, 200, 300, 400, 500])
Как проверить
view = prices[::2]
fancy = prices[[0, 2, 4]]
print(view.base is prices)             # у представления base — исходный массив
print(fancy.base is None)              # у копии собственные данные
np.shares_memory(prices, view), np.shares_memory(prices, fancy)
Вывод
True
True
(True, False)
reshape — тоже представление
grid = prices.reshape(5, 1)
grid[0, 0] = -1             # reshape тоже даёт представление
prices
Вывод
array([ -1, 200, 300, 400, 500])

Подводные камни

Функция меняет массив вызывающего

Массив передаётся в функцию по ссылке. Операции на месте (+=, /=, запись по индексу) внутри функции меняют исходные данные. Если это не задумано — начинайте функцию с a = a.copy() или пишите a = a / a.max().

Нормализация внутри функции
def normalize(a):
    a /= a.max()            # на месте: меняет массив вызывающего
    return a

data = np.array([2.0, 4.0, 8.0])
normalize(data)
data
Вывод
array([0.25, 0.5 , 1.  ])

Маленький срез держит в памяти большой массив

Пока жив срез, жив и весь исходный массив. Если из большого массива нужна маленькая часть надолго, сделайте .copy().

Срез и base
big = np.arange(1_000_000)
first = big[:3]             # держит ссылку на весь миллион элементов
print(first.base.size)
first = big[:3].copy()      # после копии большой массив можно освободить
print(first.base)
Вывод
1000000
None