Spec-Zone.ru › Python 3.11

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

Некоторые объекты в 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
Часть Стабильной ABI (включая все члены) начиная с версии 3.11.
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 присутствует в результате запроса PyBUF_SIMPLE или PyBUF_WRITABLE, потребитель должен игнорировать itemsize и предполагать itemsize == 1.

const char *format

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

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

int ndim

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

Py_ssize_t *shape

Массив Py_ssize_t длиной ndim, указывающий форму памяти в виде многомерного массива. Обратите внимание, что 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

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

Константы:

PyBUF_MAX_NDIM

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

END_OF_DOCUMENT_MARKER

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

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

массив, шаги, смещения

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

Запрос

массив

шаги

смещения

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

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):
    """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: массив, шаги и смещения

В дополнение к стандартным элементам массивы в стиле PIL могут содержать указатели, за которыми необходимо следовать, чтобы получить следующий элемент в измерении. Например, обычный трехмерный массив C char v[2][2][3] также может быть представлен как массив из 2 указателей на 2 двухмерных массива: char (*v[2])[2][3]. В представлении смещений эти два указателя могут быть встроены в начало 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)
Часть Стабильного API с версии 3.11.

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

int PyObject_GetBuffer(PyObject *exporter, Py_buffer *view, int flags)
Часть Стабильного API с версии 3.11.

Отправляет запрос exporter для заполнения view в соответствии с flags. Если экспортер не может предоставить буфер нужного типа, он ДОЛЖЕН поднять 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)
Часть Стабильного API с версии 3.11.

Освобождает буфер view и освобождает сильную ссылку (т.е. уменьшает счетчик ссылок) на поддерживающий объект представления, view->obj. Эта функция ОБЯЗАТЕЛЬНО должна быть вызвана, когда буфер больше не используется, иначе могут возникнуть утечки памяти.

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

Py_ssize_t PyBuffer_SizeFromFormat(const char *format)
Часть Стабильного API с версии 3.11.

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

Введено в версии 3.9.

int PyBuffer_IsContiguous(const Py_buffer *view, char order)
Часть Стабильного API с версии 3.11.

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

void *PyBuffer_GetPointer(const Py_buffer *view, const Py_ssize_t *indices)
Часть Стабильного API с версии 3.11.

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

int PyBuffer_FromContiguous(const Py_buffer *view, const void *buf, Py_ssize_t len, char fort)
Часть Стабильного API с версии 3.11.

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

int PyBuffer_ToContiguous(void *buf, const Py_buffer *src, Py_ssize_t len, char order)
Часть Стабильного API с версии 3.11.

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

Функция завершается с ошибкой, если len != src->len.

int PyObject_CopyData(PyObject *dest, PyObject *src)
Часть Стабильного API с версии 3.11.

Копирует данные из src в буфер dest. Может осуществлять преобразование между буферами стиля C и Fortran.

0 возвращается при успехе, -1 — при ошибке.

void PyBuffer_FillContiguousStrides(int ndims, Py_ssize_t *shape, Py_ssize_t *strides, int itemsize, char order)
Часть Стабильного API с версии 3.11.

Заполняет массив 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)
Часть Стабильного API с версии 3.11.

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

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

При успехе установит view->obj в новую ссылку на exporter и вернёт 0. В противном случае поднимет BufferError, установит view->obj в NULL и вернёт -1.

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

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

Spec-Zone.ru

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