Spec-Zone.ru › Python 3.8

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

Некоторые объекты в 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().

Py_buffer
void *buf

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

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

void *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

Строка с завершением 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. См. сложные массивы для получения дополнительной информации.

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

Py_ssize_t *suboffsets

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

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

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

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

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 можно объединить с любыми флагами, кроме 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)

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

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

Отправляет запрос exporter для заполнения view в соответствии со спецификацией flags. Если exporter не может предоставить буфер нужного типа, он ДОЛЖЕН вызвать исключение 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' или 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 на новую ссылку на exporter и вернуть 0. В противном случае вызвать PyExc_BufferError, установить view->obj в NULL и вернуть -1;

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

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

Spec-Zone.ru

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