Буферный протокол

Некоторые объекты, доступные в Python, обеспечивают доступ к базовому массиву памяти или buffer. К таким объектам относятся встроенные bytes и bytearray, а также некоторые типы расширений, например array.array. Сторонние библиотеки могут определять свои собственные типы для специальных целей, таких как обработка изображений или числовой анализ.

Хотя каждый из этих типов имеет свою собственную семантику, они имеют общую характеристику поддержки возможно большим буфером памяти. В некоторых случаях желательно получить доступ к этому буферу напрямую и без промежуточного копирования.

Python предоставляет такую возможность на уровне C в форме буферный протокол. У этого протокола есть две стороны:

  • на стороне производителя тип может экспортировать «интерфейс буфера», который позволяет объектам этого типа предоставлять информацию о своем нижележащем буфере. Данный интерфейс описан в разделе Структуры буферных объектов;
  • на стороне потребителя доступно несколько средств для получения указателя на необработанные базовые данные объекта (например, параметр метода).

Простые объекты, такие как bytes и bytearray, предоставляют свой базовый буфер в байтовой форме. Возможны другие формы; например, элементы, отображаемые array.array, могут быть многобайтовыми значениями.

Примером потребителя интерфейса буфера является метод файловых объектов write(): любой объект, который может экспортировать серию байтов через интерфейс буфера, может быть записан в файл. В то время как write() требуется доступ только для чтения к внутреннему содержимому переданного ему объекта, другим методам, таким как readinto(), требуется доступ на запись к содержимому своего аргумента. Интерфейс буфера позволяет объектам выборочно разрешать или отклонять экспорт буферов для чтения-записи и только для чтения.

У потребителя интерфейса буфера есть два способа получить буфер над целевым объектом:

В обоих случаях необходимо вызвать PyBuffer_Release(), когда буфер больше не нужен. Невыполнение этого требования может привести к различным проблемам, таким как утечка ресурсов.

Буферная структура

Структуры буферов (или просто «буферы») полезны как способ предоставить программисту Python двоичные данные из другого объекта. Их также можно использовать в качестве механизма нарезки с нулевым копированием. Используя их способность ссылаться на блок памяти, можно довольно легко предоставить программисту Python любые данные. Память может быть большим постоянным массивом в расширении C, это может быть необработанный блок памяти для манипуляций перед передачей в библиотеку операционной системы, или она может использоваться для передачи структурированных данных в их собственном формате в памяти. .

В отличие от большинства типов данных, предоставляемых интерпретатором Python, буферы являются не указателями PyObject, а скорее простыми структурами C. Это позволяет очень просто создавать и копировать их. Когда необходима универсальная оболочка для буфера, можно создать объект memoryview.

Краткие инструкции по написанию объекта экспорта см. в Структуры Буферных объектов. Для получения буфера см. PyObject_GetBuffer().

Py_buffer
void *buf

Указатель на начало логической структуры, описываемой полями буфера. Это может быть любое место в базовом блоке физической памяти экспортера. Например, с отрицательным значением strides значение может указывать на конец блока памяти.

Для смежных массивов значение указывает на начало блока памяти.

void *obj

Новая ссылка на экспортируемый объект. Ссылка принадлежит потребителю и автоматически уменьшается до NULL с помощью PyBuffer_Release(). Это поле эквивалентно возвращаемому значению любой стандартной функции C-API.

В качестве особого случая для буферов temporary, которые заключены в PyMemoryView_FromBuffer() или PyBuffer_FillInfo(), это поле — NULL. В общем, при экспорте объектов НЕ ДОЛЖНО использовать эту схему.

Py_ssize_t len

product(shape) * itemsize. Для непрерывных массивов это длина нижележащего блока памяти. Для несмежных массивов это длина, которую имела бы логическая структура, если бы она была скопирована в непрерывное представление.

Доступ к ((char *)buf)[0] up to ((char *)buf)[len-1] действителен только в том случае, если буфер был получен запросом, который гарантирует непрерывность. В большинстве случаев такой запрос будет PyBUF_SIMPLE или PyBUF_WRITABLE.

int readonly

Индикатор того, доступен ли буфер только для чтения. Это поле контролируется флагом PyBUF_WRITABLE.

Py_ssize_t itemsize

Размер одного элемента в байтах. То же, что и значение struct.calcsize(), вызванное значениями, отличными от NULL, format.

Важное исключение: если потребитель запрашивает буфер без флага PyBUF_FORMAT, для format будет установлено значение NULL, но itemsize все ещё будет иметь значение для исходного формата.

Если присутствует shape, равенство product(shape) * itemsize == len все ещё сохраняется, и потребитель может использовать itemsize для навигации по буферу.

Если shape является NULL в результате запроса PyBUF_SIMPLE или PyBUF_WRITABLE, потребитель должен игнорировать itemsize и принять itemsize == 1.

const char *format

Строка с завершением NUL в синтаксисе модульного стиля struct, описывающая содержимое одного элемента. Если это NULL, предполагается "B" (байты без знака).

Это поле контролируется флагом PyBUF_FORMAT.

int ndim

Количество измерений, которые память представляет в виде n-мерного массива. Если это 0, buf указывает на один элемент, представляющий скаляр. В этом случае shape, strides и suboffsets ДОЛЖНЫ быть NULL.

Макрос PyBUF_MAX_NDIM ограничивает максимальное количество измерений до 64. Экспортеры ДОЛЖНЫ соблюдать это ограничение, потребители многомерных буферов ДОЛЖНЫ иметь возможность обрабатывать до PyBUF_MAX_NDIM измерений.

Py_ssize_t *shape

Массив Py_ssize_t длиной ndim, указывающий форму памяти в виде n-мерного массива. Обратите внимание, что shape[0] * ... * shape[ndim-1] * itemsize ДОЛЖЕН быть равен len.

Значения формы ограничены shape[n] >= 0. Особого внимания требует корпус shape[n] == 0. См. Сложные Массивы для получения дополнительной информации.

Массив фигур доступен только для чтения.

Py_ssize_t *strides

Массив Py_ssize_t длины ndim, дающий количество байтов, которые нужно прпропустить, чтобы перейти к новому элементу в каждом измерении.

Значения шага могут быть любыми целыми числами. Для обычных массивов шаг обычно положительный, но потребитель ДОЛЖЕН уметь обрабатывать случай strides[n] <= 0. См. Сложные Массивы для получения дополнительной информации.

Массив strides доступен только для чтения для потребителя.

Py_ssize_t *suboffsets

Массив Py_ssize_t длины ndim. Если suboffsets[n] >= 0, значения, хранящиеся в n-м измерении, являются указателями, а значение подсмещения определяет, сколько байтов добавить к каждому указателю после разыменования. Отрицательное значение субсмещения указывает, что разыменования не должно происходить (переход в непрерывный блок памяти).

Если все подсмещения отрицательны (т.е. разыменование не требуется), тогда это поле должно быть NULL (значение по Умолчанию).

Данный тип представления массива используется библиотекой изображений Python (PIL). См. Сложные Массивы для получения дополнительной информации о том, как получить доступ к элементам массива.

Массив suboffsets доступен только для чтения для потребителя.

void *internal

Это предназначено для внутреннего использования экспортирующим объектом. Например, это может быть преобразовано в целое число экспортером и использовано для хранения флагов о том, должны ли быть освобождены массивы shape, strides и suboffsets при освобождении буфера. Потребитель НЕ ДОЛЖЕН изменять это значение.

Типы запросов буфера

Буферы обычно получаются путем отправки запроса на буфер экспортируемому объекту через PyObject_GetBuffer(). Поскольку сложность логической структуры памяти может сильно различаться, потребитель использует аргумент flags, чтобы указать точный тип буфера, который он может обрабатывать.

Все поля Py_buffer однозначно определяются типом запроса.

поля, не зависящие от запроса

Следующие поля не зависят от flags и всегда должны быть заполнены правильными значениями: obj, buf, len, itemsize, ndim.

только для чтения, формат

PyBUF_WRITABLE

Управляет полем readonly. Если установлено, экспортер ДОЛЖЕН предоставить буфер с возможностью записи или сообщить об ошибке. В противном случае экспортер МОЖЕТ предоставить буфер только для чтения или записи, но выбор ДОЛЖЕН быть согласован для всех потребителей.

PyBUF_FORMAT

Управляет полем format. Если установлено, это поле ДОЛЖНО быть заполнено правильно. В противном случае это поле ДОЛЖНО быть NULL.

PyBUF_WRITABLE может быть установлен на любой из флагов в следующем разделе. Поскольку PyBUF_SIMPLE определён как 0, PyBUF_WRITABLE можно использовать как автономный флаг для запроса простого буфера с возможностью записи.

PyBUF_FORMAT может быть | «d для любого из флагов, кроме PyBUF_SIMPLE. Последнее уже подразумевает формат B (беззнаковые байты).

форма, шаги, субсмещения

Флаги, управляющие логической структурой памяти, перечислены в порядке убывания сложности. Обратите внимание, что каждый флаг содержит все биты флагов под ним.

Запрос форма шаги субсмещения
PyBUF_INDIRECT
да да если нужно
PyBUF_STRIDES
да да NULL
PyBUF_ND
да NULL NULL
PyBUF_SIMPLE
NULL NULL NULL

запросы на смежность

C или Fortran смежные могут быть запрошены явно, с информацией о шаге и без нее. Без информации о шаге буфер должен быть C-смежным.

Запрос форма шаги субсмещения континг
PyBUF_C_CONTIGUOUS
да да NULL C
PyBUF_F_CONTIGUOUS
да да NULL F
PyBUF_ANY_CONTIGUOUS
да да NULL C или F
c:macro:: PyBUF_ND да NULL NULL C

составные запросы

Все возможные запросы полностью определяются комбинацией флагов в предыдущем разделе. Для удобства протокол буферов предоставляет часто используемые комбинации как отдельные флаги.

В следующей таблице U означает неопределенную непрерывность. Потребитель должен вызвать в PyBuffer_IsContiguous(), чтобы определить смежность.

Запрос форма шаги субсмещения континг только для чтения формат
PyBUF_FULL
да да если нужно U 0 да
PyBUF_FULL_RO
да да если нужно U 1 или 0 да
PyBUF_RECORDS
да да NULL U 0 да
PyBUF_RECORDS_RO
да да NULL U 1 или 0 да
PyBUF_STRIDED
да да NULL U 0 NULL
PyBUF_STRIDED_RO
да да NULL U 1 или 0 NULL
PyBUF_CONTIG
да NULL NULL C 0 NULL
PyBUF_CONTIG_RO
да NULL NULL C 1 или 0 NULL

Сложные Массивы

NumPy-стиль: форма и успехи

Логическая структура массивов в стиле NumPy определяется itemsize, ndim, shape и strides.

Если ndim == 0, ячейка памяти, на которую указывает buf, интерпретируется как скаляр размера itemsize. В этом случае и shape, и strides являются NULL.

Если strides — это NULL, массив интерпретируется как стандартный n-мерный C-массив. В противном случае потребитель должен получить доступ к n-мерному массиву следующим образом:

ptr = (char *)buf + indices[0] * strides[0] + ... + indices[n-1] * strides[n-1];
item = *((typeof(item) *)ptr);

Как отмечалось выше, buf может указывать на любое место в пределах фактического блока памяти. Экспортер может проверить действительность буфера с помощью этой функции:

def verify_structure(memlen, itemsize, ndim, shape, strides, offset):
    """Убедитесь, что параметры представляют допустимый
       массив в пределах аллоцированный памяти:
           char *mem: начало блока физической памяти
           memlen: длина блока физической памяти
           смещения: (char *)buf - mem
    """
    if offset % itemsize:
        return False
    if offset < 0 or offset+itemsize > memlen:
        return False
    if any(v % itemsize for v in strides):
        return False

    if ndim <= 0:
        return ndim == 0 and not shape and not strides
    if 0 in shape:
        return True

    imin = sum(strides[j]*(shape[j]-1) for j in range(ndim)
               if strides[j] <= 0)
    imax = sum(strides[j]*(shape[j]-1) for j in range(ndim)
               if strides[j] > 0)

    return 0 <= offset+imin and offset+imax+itemsize <= memlen

Стиль PIL: форма, шаги и частичные смещения

В дополнение к обычным элементам массивы в стиле PIL могут содержать указатели, которым необходимо следовать, чтобы перейти к следующему элементу в измерении. Например, обычный трехмерный C-массив char v[2][2][3] также можно рассматривать как массив из 2 указателей на 2 двумерных массива: char (*v[2])[2][3]. В представлении подсмещения эти два указателя могут быть встроены в начало buf, указывая на два массива char x[2][3], которые могут быть расположены в любом месте памяти.

Вот функция, которая возвращает указатель на элемент в массиве N-D, на который указывает N-мерный индекс, когда есть как шаги, так и подсмещения, отличные от NULL:

void *get_item_pointer(int ndim, void *buf, Py_ssize_t *strides,
                       Py_ssize_t *suboffsets, Py_ssize_t *indices) {
    char *pointer = (char*)buf;
    int i;
    for (i = 0; i < ndim; i++) {
        pointer += strides[i] * indices[i];
        if (suboffsets[i] >=0 ) {
            pointer = *((char**)pointer) + suboffsets[i];
        }
    }
    return (void*)pointer;
}

Функции, связанные с буфером

int PyObject_CheckBuffer(PyObject *obj)

Возвращает 1, если obj поддерживает интерфейс буфера, иначе 0. Если возвращается 1, это не гарантирует успеха PyObject_GetBuffer(). Эта функция всегда успешна.

int PyObject_GetBuffer(PyObject *exporter, Py_buffer *view, int flags)

Отправить запрос на exporter, чтобы заполнить view, как указано в flags. Если экспортер не может предоставить буфер точного типа, он ДОЛЖЕН поднять PyExc_BufferError, установить view->obj на NULL и возвращает -1.

В случае успеха заполняет view, устанавливает view->obj на новую ссылку на exporter и возвращает 0. В случае связанных поставщиков буферов, которые перенаправляют запросы на один объект, view->obj МОЖЕТ ссылаться на данный объект вместо exporter (см. Структуры Буферных объектов).

Успешные вызовы на PyObject_GetBuffer() должны сочетаться с вызовами на PyBuffer_Release(), аналогично malloc() и free(). Таким образом, после того, как потребитель закончит работу с буфером, PyBuffer_Release() должен быть вызван ровно один раз.

void PyBuffer_Release(Py_buffer *view)

Освободить буфер view и уменьшите счётчик ссылок для view->obj. Эта функция ДОЛЖНА быть вызвана, когда буфер больше не используется, иначе могут возникнуть утечки ссылок.

Ошибка вызова этой функции для буфера, который не был получен через PyObject_GetBuffer().

Py_ssize_t PyBuffer_SizeFromFormat(const char *)

Возвращает подразумеваемый itemsize из format. Эта функция ещё не реализована.

int PyBuffer_IsContiguous(Py_buffer *view, char order)

Возвращает 1, если память, определяемая view, является C-стилем (order'C') или Fortran-стилем (order'F') смежный или одним из них (order'A'). В противном случае возвращает 0. Эта функция всегда выполняется успешно.

void* PyBuffer_GetPointer(Py_buffer *view, Py_ssize_t *indices)

Получить область памяти, на которую указывает indices внутри данного view. indices должен указывать на массив индексов view->ndim.

int PyBuffer_FromContiguous(Py_buffer *view, void *buf, Py_ssize_t len, char fort)

Скопировать непрерывные байты len из buf в view. fort может быть 'C' или 'F' (для заказа в стиле C или Fortran). 0 возвращается в случае успеха, -1 — в случае ошибки.

int PyBuffer_ToContiguous(void *buf, Py_buffer *src, Py_ssize_t len, char order)

Скопировать байты len из src в его непрерывное представление в buf. order может быть 'C', 'F' или 'A' (для порядка в стиле C, Fortran или одного из них). 0 возвращается в случае успеха, -1 — в случае ошибки.

Эта функция не работает, если len! = src->len.

void PyBuffer_FillContiguousStrides(int ndims, Py_ssize_t *shape, Py_ssize_t *strides, int itemsize, char order)

Заполнить массив strides байтовыми шагами смежного массива (в стиле C, если order — это 'C', или в стиле Фортрана, если order — это 'F') массива заданной формы с заданным количеством байтов на Элемент.

int PyBuffer_FillInfo(Py_buffer *view, PyObject *exporter, void *buf, Py_ssize_t len, int readonly, int flags)

Обработка запросов буфера для экспортера, который хочет предоставить buf размера len с возможностью записи, установленной в соответствии с readonly. buf интерпретируется как последовательность байтов без знака.

Аргумент flags указывает тип запроса. Эта функция всегда заполняет view, как указано флагами, если только buf не был определён как доступный только для чтения, а PyBUF_WRITABLE не установлен в flags.

В случае успеха устанавливает view->obj на новую ссылку на exporter и возвращает 0. В противном случае поднимается PyExc_BufferError, устанавливает view->obj на NULL и возвращает -1;

Если эта функция используется как часть getbufferproc, exporter ДОЛЖЕН быть установлен для объекта экспорта, а flags должен передаваться без изменений. В противном случае exporter ДОЛЖЕН быть NULL.