Spec-Zone.ru › Python 3.12

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

Некоторые объекты в 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-го измерения, являются указателями, и значение смещения указывает, сколько байтов нужно добавить к каждому указателю после разыменования. Отрицательное значение смещения указывает, что разыменование не должно выполняться (шаги по смежному блоку памяти).

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

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

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

void *internal

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

Константы:

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)
Часть Стабильной 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.12/c-api/buffer.html

Spec-Zone.ru

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