Протокол интерфейса массива#

Примечание

Эта страница описывает специфический для NumPy API для доступа к содержимому массива NumPy из других C-расширений. PEP 3118The 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 может содержать (Python int является C long). Код, использующий этот атрибут, должен обрабатывать это соответствующим образом: либо вызывая ошибку при возможном переполнении, либо используя long long как C-тип для форм.

typestr (обязательно)

Строка, предоставляющая базовый тип однородного массива. Базовый строковый формат состоит из 3 частей: символ, описывающий порядок байтов данных (<: little-endian, >: big-endian, |: нерелевантно), символьный код, указывающий базовый тип массива, и целое число, предоставляющее количество байт, используемых типом.

Основные символьные коды типов:

t

Битовое поле (следующее целое число указывает количество битов в битовом поле).

b

Логический (целочисленный тип, где все значения только True или False)

i

Целое число

u

Беззнаковое целое число

f

Числа с плавающей точкой

c

Комплексное число с плавающей запятой

m

Timedelta

M

Datetime

O

Объект (т.е. память содержит указатель на PyObject)

S

Строка (фиксированная последовательность символов)

U

Unicode (фиксированная последовательность Py_UCS4)

V

Другое (void * – каждый элемент является блоком памяти фиксированного размера)

descr (опционально)

Список кортежей, предоставляющих более детальное описание структуры памяти для каждого элемента в однородном массиве. Каждый кортеж в списке имеет два или три элемента. Обычно этот атрибут используется, когда typestr является V[0-9]+, но это не является обязательным требованием. Единственное требование — чтобы количество байтов, представленных в typestr ключ совпадает с общим количеством байтов, представленных здесь. Идея заключается в поддержке описаний C-подобных структур, составляющих элементы массива. Элементы каждого кортежа в списке — это

  1. Строка, предоставляющая имя, связанное с этой частью типа данных. Это также может быть кортеж из ('full name', 'basic_name') где basic name будет допустимым именем переменной Python, представляющим полное имя поля.

  2. Либо строка описания базового типа, как в typestr или другой список (для вложенных структурированных типов)

  3. Необязательный кортеж формы, указывающий, сколько раз эта часть структуры должна повторяться. Повторы не предполагаются, если он не задан. Очень сложные структуры можно описать с помощью этого универсального интерфейса. Однако обратите внимание, что каждый элемент массива всё ещё имеет тот же тип данных. Ниже приведены некоторые примеры использования этого интерфейса.

По умолчанию: [('', 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 (непрерывного) или кортежа шагов, который указывает количество байтов, необходимое для перехода к следующему элементу массива в соответствующем измерении. Каждая запись должна быть целым числом (Python int). Как и с формой, значения могут быть больше, чем может быть представлено в C int или 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 был очень похож. Различия были в основном эстетическими. В частности:

  1. Структура PyArrayInterface не имела члена descr в конце (и, следовательно, флага ARR_HAS_DESCR)

  2. 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__ капсулы содержали этот кортеж содержимого.

  3. Кортеж, возвращаемый из __array_interface__['data'] раньше был шестнадцатеричной строкой (теперь это целое число или длинное целое число).

  4. Не было __array_interface__ атрибут вместо всех ключей (кроме версии) в __array_interface__ словарь был их собственным атрибутом: Таким образом, чтобы получить информацию на стороне Python, вам приходилось отдельно обращаться к атрибутам:

    • __array_data__

    • __array_shape__

    • __array_strides__

    • __array_typestr__

    • __array_descr__

    • __array_offset__

    • __array_mask__