Spec-Zone.ru › Python 3.10

Протокол буфера

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

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

Python предоставляет такой механизм на уровне C в виде протокола буфера. Этот протокол имеет две стороны:

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

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

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

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

  • вызов PyObject_GetBuffer() с соответствующими параметрами;
  • вызов PyArg_ParseTuple() (или одного из его аналогов) с одним из y*, w* или s* форматов кодов.

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

Структура буфера

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

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

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

type Py_buffer
void *buf

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

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

PyObject *obj

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

В качестве специального случая, для временных буферов, которые обернуты с помощью 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. Экспортеры ДОЛЖНЫ соблюдать это ограничение, потребители многомерных буферов ДОЛЖНЫ быть способны обрабатывать до 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. См. сложные массивы для получения дополнительной информации.

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

Py_ssize_t *suboffsets

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

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

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

Массив смещений является только для чтения для потребителя.

void *internal

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

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

Буферы обычно получаются путем отправки запроса на буфер объекту-экспортеру через 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 может быть объединено с любыми флагами, кроме PyBUF_SIMPLE. Последний уже подразумевает формат B (незнаковые байты).

shape, strides, suboffsets

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

Запрос

shape

strides

suboffsets

PyBUF_INDIRECT

да

да

при необходимости

PyBUF_STRIDES

да

да

NULL

PyBUF_ND

да

NULL

NULL

PyBUF_SIMPLE

NULL

NULL

NULL

Запросы на непрерывность

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

Запрос

shape

strides

suboffsets

contig

PyBUF_C_CONTIGUOUS

да

да

NULL

C

PyBUF_F_CONTIGUOUS

да

да

NULL

F

PyBUF_ANY_CONTIGUOUS

да

да

NULL

C или F

PyBUF_ND

да

NULL

NULL

C

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

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

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

Запрос

shape

strides

suboffsets

contig

только чтение

формат

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: shape и strides

Логическая структура массивов в стиле 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):
    """Verify that the parameters represent a valid array within
       the bounds of the allocated memory:
           char *mem: start of the physical memory block
           memlen: length of the physical memory block
           offset: (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: shape, strides и suboffsets

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

Вот функция, которая возвращает указатель на элемент в n-мерном массиве, на который указывает 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)

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

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

Успешные вызовы 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 *format)

Возвращает предполагаемый itemsize из format. При ошибке генерируется исключение и возвращается -1.

Добавлена в версии 3.9.

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' или стиль Fortran, если 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 в соответствии с flags, если buf не назначен только для чтения, и PyBUF_WRITABLE не установлен в flags.

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

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

© 2001–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.10/c-api/buffer.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API