Протокол буфера
Некоторые объекты в 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 -
-
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(), вызываемому для значений не-NULLformat.Важное исключение: если потребитель запрашивает буфер без флага
PyBUF_FORMAT,formatбудет установлено вNULL, ноitemsizeпо-прежнему сохранит значение исходного формата.Если
shapeприсутствует, равенствоproduct(shape) * itemsize == lenвсё ещё выполняется, и потребитель может использоватьitemsizeдля навигации по буферу.Если
shapeимеет значениеNULLв результате запроса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. Экспортеры ДОЛЖНЫ соблюдать это ограничение, потребители многомерных буферов ДОЛЖНЫ быть способны обрабатывать до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 -
Это используется внутри экспортирующего объекта. Например, это может быть преобразовано экспортером в целое число и использоваться для хранения флагов о том, нужно ли освобождать массивы формы, шагов и смещений при освобождении буфера. Потребитель НЕ должен изменять это значение.
-
Типы запросов на буферы
Буферы обычно получаются путем отправки запроса на буфер объекту-экспортеру через 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 |
|---|---|---|---|
| да | да | при необходимости |
| да | да | NULL |
| да | NULL | NULL |
| NULL | NULL | NULL |
Запросы на непрерывность
C или Fortran непрерывность может быть явно запрошена, с и без информации о шагах. Без информации о шагах буфер должен быть непрерывным по C.
Запрос | shape | strides | suboffsets | contig |
|---|---|---|---|---|
| да | да | NULL | C |
| да | да | NULL | F |
| да | да | NULL | C или F |
да | NULL | NULL | C |
Составные запросы
Все возможные запросы полностью определяются некоторой комбинацией флагов в предыдущем разделе. Для удобства протокол буфера предоставляет часто используемые комбинации в виде отдельных флагов.
В следующей таблице U обозначает неопределенную непрерывность. Потребителю необходимо будет вызвать PyBuffer_IsContiguous() для определения непрерывности.
Запрос | shape | strides | suboffsets | contig | только чтение | формат |
|---|---|---|---|---|---|---|
| да | да | при необходимости | U | 0 | да |
| да | да | при необходимости | U | 1 или 0 | да |
| да | да | NULL | U | 0 | да |
| да | да | NULL | U | 1 или 0 | да |
| да | да | NULL | U | 0 | NULL |
| да | да | NULL | U | 1 или 0 | NULL |
| да | NULL | NULL | C | 0 | NULL |
| да | 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, так и смещения:
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;
}
© 2001–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.10/c-api/buffer.html