NumPy: вычисления и устройство массивов

View и copy: предсказание влияния изменений среза на исходный массив

Содержание курса

Предсказание и наблюдение мутации через view и copy

Алгоритм предсказания сводится к трём шагам:

  1. Определить, как получен объект. Срез start:stop:step — view. Вызов .copy() — независимое хранилище.
  2. Если view — любая запись в него затрагивает исходный массив в соответствующих позициях.
  3. Если копия — исходный массив не изменится ни при каких мутациях копии.

Чтобы алгоритм был реально применим, нужно уметь заранее называть конкретные индексы, которые изменятся. Разберём это на примере с шаговым срезом и двумерным массивом — они чуть менее очевидны, чем простой a[1:4].

Шаговый срез: предсказываем затронутые индексы

import numpy as np

a = np.array([0, 10, 20, 30, 40, 50, 60])
v = a[1:7:2]   # элементы с индексами 1, 3, 5 → значения 10, 30, 50

# Предсказание:
# v[0] → a[1], v[1] → a[3], v[2] → a[5]
# Меняем v[1] = 99 → ожидаем a[3] == 99, остальные не трогаем
v[1] = 99
print(a)   # [ 0 10 20 99 40 50 60]

Прежде чем запустить код, стоит явно выписать соответствие: v[k] отображается на a[start + k * step]. Здесь start=1, step=2, поэтому v[1]a[1 + 1*2] = a[3]. Предсказание подтверждается наблюдением.

Теперь мутация в обратную сторону — из исходного массива в view:

a[5] = 777   # a[5] входит в срез v как v[2]
print(v)     # [ 10  99 777]

Общая область памяти работает в обе стороны: запись через a видна в v точно так же, как запись через v видна в a.

Двумерный массив: срез строк — тоже view

m = np.array([[1, 2, 3],
              [4, 5, 6],
              [7, 8, 9]])

rows = m[0:2]   # первые две строки — view

# Предсказание: rows[1, 2] → m[1, 2]
# Меняем rows[1, 2] = 99 → ожидаем m[1, 2] == 99
rows[1, 2] = 99
print(m)
# [[1 2 3]
#  [4 5 99]
#  [7 8 9]]

Прямоугольный срез по строкам (синтаксис start:stop без fancy indexing) возвращает view — это тот же принцип, что и для одномерного случая.

Когда нужна изоляция: копия блокирует мутацию

m2 = np.array([[1, 2, 3],
               [4, 5, 6],
               [7, 8, 9]])

rows_copy = m2[0:2].copy()
rows_copy[1, 2] = 99   # меняем копию
print(m2)
# [[1 2 3]
#  [4 5 6]   ← не изменилось
#  [7 8 9]]

Если вы не уверены, является ли объект view или копией — проверьте np.shares_memory до того, как что-то менять. Это особенно важно, когда объект передан в функцию и вы не контролируете, как он был создан.