Протокол интерфейса массива#
Примечание
Эта страница описывает специфический для NumPy API для доступа к содержимому
массива NumPy из других C-расширений. PEP 3118 –
The Revised Buffer Protocol вводит аналогичный, стандартизированный API для использования любым модулем расширения. Cythonподдержка буферного массива использует PEP 3118 API; см. Cython NumPy
руководство.
- версия:
3
Интерфейс массива (иногда называемый протоколом массива) был создан в 2005 году как средство для массивоподобных объектов Python интеллектуально повторно использовать буферы данных друг друга, когда это возможно. Однородный N-мерный интерфейс массива является механизмом по умолчанию для объектов, чтобы делиться памятью и информацией N-мерных массивов. Интерфейс состоит из Python-стороны и C-стороны, использующих два атрибута. Объекты, желающие считаться N-мерными массивами в коде приложения, должны поддерживать хотя бы один из этих атрибутов. Объекты, желающие поддерживать N-мерный массив в коде приложения, должны искать хотя бы один из этих атрибутов и использовать предоставленную информацию соответствующим образом.
Этот интерфейс описывает однородные массивы в том смысле, что каждый элемент массива имеет одинаковый «тип». Этот тип может быть очень простым или представлять собой довольно произвольную и сложную структуру, подобную C.
Существует два способа использования интерфейса: со стороны Python и со стороны C. Оба являются отдельными атрибутами.
Сторона Python#
Этот подход к интерфейсу состоит в том, что объект имеет
__array_interface__ атрибут.
- объект.__array_interface__#
Словарь элементов (3 обязательных и 5 опциональных). Опциональные ключи в словаре имеют подразумеваемые значения по умолчанию, если они не предоставлены.
Ключами являются:
- shape (обязательно)
Кортеж, элементы которого представляют размер массива в каждом измерении. Каждая запись является целым числом (Python
int). Обратите внимание, что эти целые числа могут быть больше, чем платформаintилиlongможет содержать (Pythonintявляется Clong). Код, использующий этот атрибут, должен обрабатывать это соответствующим образом: либо вызывая ошибку при возможном переполнении, либо используяlong longкак C-тип для форм.- typestr (обязательно)
Строка, предоставляющая базовый тип однородного массива. Базовый строковый формат состоит из 3 частей: символ, описывающий порядок байтов данных (
<: little-endian,>: big-endian,|: нерелевантно), символьный код, указывающий базовый тип массива, и целое число, предоставляющее количество байт, используемых типом.Основные символьные коды типов:
tБитовое поле (следующее целое число указывает количество битов в битовом поле).
bЛогический (целочисленный тип, где все значения только
TrueилиFalse)iЦелое число
uБеззнаковое целое число
fЧисла с плавающей точкой
cКомплексное число с плавающей запятой
mTimedelta
MDatetime
OОбъект (т.е. память содержит указатель на
PyObject)SСтрока (фиксированная последовательность символов)
UUnicode (фиксированная последовательность
Py_UCS4)VДругое (void * – каждый элемент является блоком памяти фиксированного размера)
- descr (опционально)
Список кортежей, предоставляющих более детальное описание структуры памяти для каждого элемента в однородном массиве. Каждый кортеж в списке имеет два или три элемента. Обычно этот атрибут используется, когда typestr является
V[0-9]+, но это не является обязательным требованием. Единственное требование — чтобы количество байтов, представленных в typestr ключ совпадает с общим количеством байтов, представленных здесь. Идея заключается в поддержке описаний C-подобных структур, составляющих элементы массива. Элементы каждого кортежа в списке — этоСтрока, предоставляющая имя, связанное с этой частью типа данных. Это также может быть кортеж из
('full name', 'basic_name')где basic name будет допустимым именем переменной Python, представляющим полное имя поля.Либо строка описания базового типа, как в typestr или другой список (для вложенных структурированных типов)
Необязательный кортеж формы, указывающий, сколько раз эта часть структуры должна повторяться. Повторы не предполагаются, если он не задан. Очень сложные структуры можно описать с помощью этого универсального интерфейса. Однако обратите внимание, что каждый элемент массива всё ещё имеет тот же тип данных. Ниже приведены некоторые примеры использования этого интерфейса.
По умолчанию:
[('', typestr)]- данные
Кортеж из двух элементов, первый аргумент которого — Целое число Python который указывает на область данных, хранящую содержимое массива.
Примечание
При преобразовании из C/C++ через
PyLong_From*или высокоуровневые привязки, такие как Cython или pybind11, убедитесь, что используете целое число достаточной разрядности.Этот указатель должен указывать на первый элемент данных (другими словами, любое смещение всегда игнорируется в этом случае). Вторая запись в кортеже — это флаг только для чтения (true означает, что область данных доступна только для чтения).
Этот атрибут также может быть объектом, предоставляющим интерфейс буфера который будет использоваться для совместного использования данных. Если этот ключ
None, тогда совместное использование памяти будет осуществляться через интерфейс буфера самого объекта. В этом случае ключ offset может использоваться для указания начала буфера. Ссылка на объект, предоставляющий интерфейс массива, должна храниться новым объектом, если область памяти должна быть защищена.Примечание
Не указание этого поля использует «скалярный» путь, который мы можем удалить в будущем, так как мы не знаем о каких-либо пользователях. В этом случае NumPy присваивает исходный объект как скаляр в массив.
Изменено в версии 2.4: До NumPy 2.4
NULLуказатель использовал недокументированный «скалярный» путь и поэтому обычно не принимался (и вызывал сбои на некоторых путях). После NumPy 2.4,NULLпринимается, хотя NumPy создаст новое выделение памяти размером 1 байт для массива.- strides (опционально)
Либо
Noneдля указания массива в стиле C (непрерывного) или кортежа шагов, который указывает количество байтов, необходимое для перехода к следующему элементу массива в соответствующем измерении. Каждая запись должна быть целым числом (Pythonint). Как и с формой, значения могут быть больше, чем может быть представлено в Cintилиlong; вызывающий код должен обрабатывать это соответствующим образом, либо выдавая ошибку, либо используяlong longв C. По умолчаниюNoneчто подразумевает C-стиль непрерывного буфера памяти. В этой модели последнее измерение массива изменяется быстрее всего. Например, кортеж шагов по умолчанию для объекта, записи массива которого имеют длину 8 байт и форма которого(10, 20, 30)будет(4800, 240, 8).По умолчанию:
None(C-стиль смежный)- маска (опционально)
Noneили объект, предоставляющий интерфейс массива. Все элементы массива-маски должны интерпретироваться только как истинные или не истинные, указывая, какие элементы этого массива являются допустимыми. Форма этого объекта должна быть “broadcastable” к форме исходного массива.По умолчанию:
None(Все значения массива допустимы)- смещение (опционально)
Целочисленное смещение в область данных массива. Это можно использовать только когда данные
Noneили возвращаетmemoryviewобъект.По умолчанию:
0.- версия (обязательно)
Целое число, показывающее версию интерфейса (например, 3 для этой версии). Будьте осторожны, не используйте это для аннулирования объектов, представляющих будущие версии интерфейса.
Доступ к C-структуре#
Этот подход к интерфейсу массива позволяет быстрее получать доступ к массиву, используя только один поиск атрибута и хорошо определенную C-структуру.
- объект.__array_struct__#
A
PyCapsuleчейpointerчлен содержит указатель на заполненныйPyArrayInterfaceструктуры. Память для структуры выделяется динамически, иPyCapsuleтакже создается с соответствующим деструктором, так что получатель этого атрибута просто должен применитьPy_DECREFк объекту, возвращаемому этим атрибутом, когда он завершен. Также, либо данные должны быть скопированы, либо должна быть сохранена ссылка на объект, предоставляющий этот атрибут, чтобы гарантировать, что данные не освобождены. Объекты, предоставляющие__array_struct__интерфейс также не должен перераспределять память, если другие объекты ссылаются на них.
The PyArrayInterface структура определена в numpy/ndarrayobject.h
как:
typedef struct {
int two; /* contains the integer 2 -- simple sanity check */
int nd; /* number of dimensions */
char typekind; /* kind in array --- character code of typestr */
int itemsize; /* size of each element */
int flags; /* flags indicating how the data should be interpreted */
/* must set ARR_HAS_DESCR bit to validate descr */
Py_ssize_t *shape; /* A length-nd array of shape information */
Py_ssize_t *strides; /* A length-nd array of stride information */
void *data; /* A pointer to the first element of the array */
PyObject *descr; /* NULL or data-description (same as descr key
of __array_interface__) -- must set ARR_HAS_DESCR
flag or this will be ignored. */
} PyArrayInterface;
Член flags может состоять из 5 битов, показывающих, как данные должны быть
интерпретированы, и одного бита, показывающего, как интерфейс должен быть
интерпретирован. Биты данных NPY_ARRAY_C_CONTIGUOUS (0x1),
NPY_ARRAY_F_CONTIGUOUS (0x2), NPY_ARRAY_ALIGNED (0x100),
NPY_ARRAY_NOTSWAPPED (0x200), и NPY_ARRAY_WRITEABLE (0x400). Финальный флаг
NPY_ARR_HAS_DESCR (0x800) указывает, имеет ли эта структура
поле arrdescr. К этому полю не следует обращаться, если этот
флаг отсутствует.
-
NPY_ARR_HAS_DESCR#
Новое с 16 июня 2006 года:
В прошлом большинство реализаций использовали desc член PyCObject
(теперь PyCapsule) сам (не путайте это с членом “descr”
в PyArrayInterface структура выше — это две отдельные
вещи) для хранения указателя на объект, предоставляющий интерфейс.
Теперь это явная часть интерфейса. Обязательно возьмите
ссылку на объект и вызовите PyCapsule_SetContext перед возвращением PyCapsule, и настроить деструктор для уменьшения этой
ссылки.
Примечание
__array_struct__ считается устаревшим и не должен использоваться в новом
коде. Используйте буферный протокол или протокол DLPack
numpy.from_dlpack вместо этого.
Примеры описания типов#
Для ясности полезно привести несколько примеров описания типа
и соответствующих __array_interface__ записи 'descr'. Спасибо Скотту Гилберту за эти примеры:
В каждом случае ключ 'descr' является необязательным, но, конечно, предоставляет больше информации, которая может быть важна для различных приложений:
* Float data
typestr == '>f4'
descr == [('','>f4')]
* Complex double
typestr == '>c8'
descr == [('real','>f4'), ('imag','>f4')]
* RGB Pixel data
typestr == '|V3'
descr == [('r','|u1'), ('g','|u1'), ('b','|u1')]
* Mixed endian (weird but could happen).
typestr == '|V8' (or '>u8')
descr == [('big','>i4'), ('little',')]
* Nested structure
struct {
int ival;
struct {
unsigned short sval;
unsigned char bval;
unsigned char cval;
} sub;
}
typestr == '|V8' (or ' if you want)
descr == [('ival','), ('sub', [('sval','), ('bval','|u1'), ('cval','|u1') ]) ]
* Nested array
struct {
int ival;
double data[16*4];
}
typestr == '|V516'
descr == [('ival','>i4'), ('data','>f8',(16,4))]
* Padded structure
struct {
int ival;
double dval;
}
typestr == '|V16'
descr == [('ival','>i4'),('','|V4'),('dval','>f8')]
Должно быть понятно, что любой структурированный тип может быть описан с использованием этого интерфейса.
pkgload, PackageLoader#
Интерфейс версии 2 был очень похож. Различия были в основном эстетическими. В частности:
Структура PyArrayInterface не имела члена descr в конце (и, следовательно, флага ARR_HAS_DESCR)
The
contextчленPyCapsule(формальноdescчленPyCObject) возвращённый из__array_struct__не был указан. Обычно это был объект, предоставляющий массив (чтобы можно было сохранить ссылку на него и уничтожить, когда C-объект был уничтожен). Теперь это явное требование, чтобы это поле использовалось каким-либо образом для хранения ссылки на владеющий объект.Примечание
До августа 2020 года здесь было написано:
Теперь это должен быть кортеж, первый элемент которого — строка с "PyArrayInterface Version #", а второй элемент — объект, представляющий массив.
Эта конструкция была отозвана почти сразу после предложения, в <https://mail.python.org/pipermail/numpy-discussion/2006-June/020995.html>. Несмотря на 14 лет документации, утверждающей обратное, никогда не было допустимо предполагать, что
__array_interface__капсулы содержали этот кортеж содержимого.Кортеж, возвращаемый из
__array_interface__['data']раньше был шестнадцатеричной строкой (теперь это целое число или длинное целое число).Не было
__array_interface__атрибут вместо всех ключей (кроме версии) в__array_interface__словарь был их собственным атрибутом: Таким образом, чтобы получить информацию на стороне Python, вам приходилось отдельно обращаться к атрибутам:__array_data____array_shape____array_strides____array_typestr____array_descr____array_offset____array_mask__