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

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

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

Срезы в NumPy вы уже использовали — синтаксис start:stop:step знаком с урока по индексации. Но за этим синтаксисом скрывается поведение, которое легко упустить: срез не копирует данные, а возвращает другой «взгляд» на те же байты в памяти. Именно это поведение определяет, что произойдёт с исходным массивом, если вы изменили срез — и этому посвящён весь урок.

View и copy: два режима доступа к памяти ndarray

Когда вы пишете v = a[1:4], NumPy не выделяет новый кусок памяти под три элемента. Вместо этого v — это отдельный объект ndarray, который знает: «мои данные начинаются вот здесь в памяти, шаг такой-то, длина такая-то». Байты, в которых хранятся числа, общие — и у a, и у v. Такой объект называется view (представление).

Последствие прямое: если вы изменяете элемент через v, вы фактически обращаетесь к тем же байтам в памяти, на которые смотрит a. Никакой «защиты» нет — это не баг, а намеренное поведение NumPy ради экономии памяти и скорости.

import numpy as np

a = np.array([10, 20, 30, 40, 50])
v = a[1:4]   # view: элементы 20, 30, 40

v[0] = 99    # меняем первый элемент view
print(a)     # [10 99 30 40 50] — исходный тоже изменился

Это и есть view в действии: v[0] и a[1] — один и тот же элемент в общей области памяти.

Если вам нужна независимая копия — объект, который хранит те же значения, но в отдельном куске памяти — нужно явно попросить об этом:

a2 = np.array([10, 20, 30, 40, 50])
c = a2[1:4].copy()   # независимая копия

c[0] = 99
print(a2)   # [10 20 30 40 50] — исходный не тронут

Метод .copy() выделяет новое хранилище и переписывает туда значения. После этого c и a2 ничего не знают друг о друге на уровне памяти.

Разница между view и copy — не в значениях (при создании они одинаковы), а в том, где эти значения живут. View смотрит в чужую память, copy владеет своей. Это разграничение и определяет всё поведение, которое мы разберём дальше.