6.2. Масив ndarray#

ndarray — це тип, що зберігає числові дані у numpy. Це дві речі в одному: єдиний щільно упакований блок даних і невеликий дескриптор перед цим блоком, що визначає, як його читати.

6.2.1. Всередині#

Блок даних зберігає всі елементи масиву підряд, без жодних додаткових даних між ними. Кожен елемент займає однакову кількість байтів — один для масиву значень uint8, два для uint16, чотири для float. 256-елементний масив uint8 займає рівно 256 байтів; ті самі 256 чисел у Python list займають кілобайт — один 32-бітний слот на елемент, незалежно від того, скільки бітів насправді потрібне значенню.

Дескриптор фіксує, що означає блок. П’яти значень достатньо для опису будь-якого прямокутного масиву, незалежно від кількості вимірів:

  • dtype — тип елемента. Визначає, скільки байтів займає кожен елемент і який діапазон значень він може зберігати (розглядається на Типи даних (dtype)).

  • itemsize — ширина одного елемента в байтах, похідна від dtype.

  • ndim — кількість вимірів (1 для вектора, 2 для матриці, 3 для об’єму, до 4).

  • shape — розмір вздовж кожного виміру у вигляді кортежу.

  • strides — як проходити блок даних, щоб обійти кожну вісь. Розглядається на Форма та кроки.

Ось і все. Кожен швидкий шлях у numpy — арифметика, редукції, трансляція, нарізка — працює безпосередньо на основі цих п’яти значень плюс вказівника на дані, без накладних витрат Python на кожен елемент.

6.2.2. Що дає ця конструкція#

Три властивості випливають з конструкції «щільний блок + невеликий дескриптор» і визначають поведінку решти розділу.

Поелементна математика виконується одним викликом. a + b між двома масивами однакової форми додає два буфери і записує третій — все в одному виклику бібліотеки. np.sin(a) робить те саме для синуса кожного елемента. Арифметичні, порівняльні та побітові оператори — всі працюють так само.

Інший погляд на ті самі дані — безкоштовний. Запит підобласті масиву або тих самих даних, структурованих за іншою формою, не переміщує жодного байта. Операція повертає новий дескриптор, що вказує на той самий блок даних. Результат називається видом — другим вікном в той самий базовий буфер. Запис через вид записує в джерело.

Різні форми все одно працюють. Коротший масив проти довшого, рядок проти матриці, стовпець проти рядка — numpy вирівнює їх через трансляцію — невеликий набір правил, що визначає, яка коротка вісь розтягується до відповідності довгій. Розтягнення — віртуальне; дані не дублюються.

6.2.3. Що коштує ця конструкція#

Два обмеження випливають з тієї самої конструкції.

Кожен елемент має однаковий тип. Список може містити int поруч із str поруч зі списком ще трьох значень int; ndarray — ні. Тип фіксується під час конструювання. На сторінці Типи даних (dtype) розглядається невеликий набір типів, що підтримує numpy, і правила, що виникають із фіксації одного типу.

Збільшення масиву — не безкоштовне. Список зберігає вільні слоти в кінці й підтримує дешевий .append. ndarray має рівно той розмір, що йому потрібен; додавання означало б виділення нового, більшого буфера та копіювання старого вмісту. Метода append() немає, і це навмисно. Правильний підхід на камері — попередньо виділити місце призначення кінцевого розміру і заповнювати його; техніка розглядається на Продуктивність.

Маючи щільний типізований буфер для даних, невеликий дескриптор для метаданих і три поведінкові гарантії (швидка поелементна математика, копії-без-копіювання тих самих даних під іншим виглядом і форми, що транслюються), ndarray є фундаментом, на якому будується весь решта розділу. Наступне практичне питання — як масив насправді з’являється: з літерала, з попередньо заповненого виділення, з буфера периферійного пристрою.