View и copy: предсказание влияния изменений среза на исходный массив
Содержание курса
Атрибут .base: иллюстрация владения памятью и его ограничения
Каждый ndarray хранит атрибут .base. Если массив владеет своими данными сам — .base равен None. Если массив смотрит в чужую память (то есть является view) — .base указывает на объект, которому эта память принадлежит.
В простом случае это выглядит понятно:
import numpy as np
a = np.array([10, 20, 30, 40, 50])
v = a[0:3] # view от a
print(v.base is a) # True — v смотрит в память a
c = a.copy()
print(c.base is None) # True — c владеет своими данными
Для этих двух случаев .base читается легко и интуитивно. На этом его удобство заканчивается.
Проблема возникает в цепочках. Если вы берёте срез от среза, .base у конечного объекта будет указывать не на ближайшего «родителя», а на того, кто реально владеет памятью — то есть на исходный массив или даже на какой-то промежуточный объект, в зависимости от внутреннего устройства NumPy:
a = np.array([10, 20, 30, 40, 50])
v1 = a[1:5] # view от a
v2 = v1[0:2] # view от v1 — но чья память?
print(v2.base is v1) # может быть False
print(v2.base is a) # может быть True
Здесь v2.base is v1 может дать False, потому что NumPy «пробрасывает» .base к реальному владельцу данных, минуя промежуточные объекты. Если вы рассчитываете на v2.base is v1 и получаете False, это не значит, что v2 — копия. Просто .base работает не так, как вы ожидаете.
Ещё одна ловушка: .base отвечает на вопрос «кто владеет памятью», но не на вопрос «разделяют ли вот эти два конкретных объекта память». Если у вас есть x и y, и вы хотите знать, связаны ли они, проверка x.base is y или y.base is x не гарантирует правильного ответа во всех сценариях.
Для этого есть np.shares_memory(x, y) — он отвечает именно на этот вопрос, без допущений о структуре цепочки.
.base полезен как быстрая иллюстрация: увидеть, что view ссылается на чужую память, а копия — нет. Но принимать на его основе решения в коде не стоит. Если нужна надёжная проверка — np.shares_memory.
