Spec-Zone.ru › Python 3.9

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

Определённые объекты в 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

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

Если все suboffset отрицательны (то есть разыменования не требуется), то это поле ДОЛЖНО быть NULL (значение по умолчанию).

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

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

void *internal

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

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 (беззнаковые байты).

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 шаги, так и suboffsets:

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') contiguous, либо любой из них (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 шагами байтов contiguous (порядка 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–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.9/c-api/buffer.html

Spec-Zone.ru

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