Spec-Zone.ru › Python 3.13

Протокол буферизации

Некоторые объекты в 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 равно NULL в результате запроса PyBUF_SIMPLE или PyBUF_WRITABLE, потребитель должен проигнорировать itemsize и предположить itemsize == 1.

char *format

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

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

int ndim

Число измерений, представляемых памятью в виде n-мерного массива. Если это 0, buf указывает на единственный элемент, представляющий скаляр. В этом случае, shape, strides и suboffsets ДОЛЖНЫ быть NULL. Максимальное число измерений задается 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-го измерения, являются указателями, и значение suboffset указывает, сколько байт нужно добавить к каждому указателю после разыменования. Отрицательное значение suboffset указывает, что разыменование не должно выполняться (шаговый доступ в непрерывном блоке памяти).

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

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

Массив смещений является неизменяемым для потребителя.

void *internal

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

Константы:

PyBUF_MAX_NDIM

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

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

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

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

Независимые от запроса поля

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

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

PyBUF_WRITABLE

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

PyBUF_FORMAT

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

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

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

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

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

Запрос

формат

шаги

смещения

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)
Часть Стабильной ABI с версии 3.11.

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

int PyObject_GetBuffer(PyObject *exporter, Py_buffer *view, int flags)
Часть Стабильной ABI с версии 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)
Часть Стабильной ABI с версии 3.11.

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

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

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

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

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

int PyBuffer_IsContiguous(const Py_buffer *view, char order)
Часть Стабильной ABI с версии 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)
Часть Стабильной ABI с версии 3.11.

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

int PyBuffer_FromContiguous(const Py_buffer *view, const void *buf, Py_ssize_t len, char fort)
Часть Стабильной ABI с версии 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)
Часть Стабильной ABI с версии 3.11.

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

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

int PyObject_CopyData(PyObject *dest, PyObject *src)
Часть Стабильной ABI с версии 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)
Часть Стабильной ABI с версии 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)
Часть Стабильной ABI с версии 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–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/c-api/buffer.html

Spec-Zone.ru

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