Буферный протокол¶
Некоторые объекты, доступные в Python, предоставляют доступ к базовому массиву памяти или буферу. К таким объектам относятся встроенные bytes
и bytearray
, а также некоторые типы расширений, такие как array.array
. Сторонние библиотеки могут определять свои собственные типы для специальных целей, таких как обработка изображений или числовой анализ.
Хотя каждый из этих типов имеет свою собственную семантику, их объединяет то, что они поддерживаются, возможно, большим объемом буфера памяти. В некоторых ситуациях желательно обращаться к этому буферу напрямую, без промежуточного копирования.
Python предоставляет такую возможность на уровне C в виде buffer protocol. Этот протокол имеет две стороны:
на стороне производителя тип может экспортировать «интерфейс буфера», который позволяет объектам этого типа предоставлять информацию об их базовом буфере. Этот интерфейс описан в разделе Структуры буферных объектов;
на стороне потребителя доступно несколько способов получения указателя на необработанные базовые данные объекта (например, параметр метода).
Простые объекты, такие как bytes
и bytearray
, предоставляют доступ к своему базовому буферу в байтовой форме. Возможны и другие формы; например, элементы, предоставляемые с помощью array.array
, могут быть многобайтовыми значениями.
Примером использования интерфейса buffer является метод write()
для файловых объектов: любой объект, который может экспортировать последовательность байт через интерфейс buffer, может быть записан в файл. В то время как write()
требуется доступ только для чтения к внутреннему содержимому переданного ему объекта, другие методы, такие как readinto()
, нуждаются в доступе на запись к содержимому своего аргумента. Интерфейс buffer позволяет объектам выборочно разрешать или отклонять экспорт буферов для чтения и записи и буферов только для чтения.
У пользователя интерфейса buffer есть два способа получить доступ к буферу над целевым объектом:
вызовите
PyObject_GetBuffer()
с нужными параметрами;вызов
PyArg_ParseTuple()
(или один из его братьев и сестер) с помощью одного изy*
,w*
илиs*
format codes.
В обоих случаях:c:func:PyBuffer_Release необходимо вызывать, когда буфер больше не нужен. Невыполнение этого требования может привести к различным проблемам, таким как утечка ресурсов.
Буферная структура¶
Буферные структуры (или просто «буферы») полезны как способ предоставления двоичных данных из другого объекта программисту на Python. Они также могут использоваться в качестве механизма нарезки с нулевой копией. Используя их способность ссылаться на блок памяти, можно довольно легко предоставить любые данные программисту на Python. Память может быть большим постоянным массивом в расширении C, это может быть необработанный блок памяти для обработки перед передачей в библиотеку операционной системы, или он может использоваться для передачи структурированных данных в их собственном формате в памяти.
В отличие от большинства типов данных, предоставляемых интерпретатором Python, буферы являются не указателями на c:type:PyObject, а простыми структурами на C. Это позволяет очень просто создавать и копировать их. Когда требуется универсальная оболочка для буфера, может быть создан объект memoryview.
Краткие инструкции по созданию экспортируемого объекта приведены в разделе Buffer Object Structures. Для получения буфера см. PyObject_GetBuffer()
.
-
type Py_buffer¶
- Part of the Стабильный ABI (including all members) since version 3.11.
-
void *buf¶
Указатель на начало логической структуры, описываемой буферными полями. Это может быть любое местоположение в базовом физическом блоке памяти экспортера. Например, при отрицательном значении
strides
значение может указывать на конец блока памяти.Для массивов contiguous значение указывает на начало блока памяти.
-
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]
возможен только в том случае, если буфер был получен с помощью запроса, гарантирующего непрерывность. В большинстве случаев таким запросом будет:c:macro: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
, потребитель должен проигнорировать:c:member:~Py_buffer.itemsize и предположить, чтоitemsize == 1
.
-
const char *format¶
Строка, заканчивающаяся NUL, в синтаксисе в стиле модуля
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
требует особого внимания. Дополнительную информацию смотрите в разделе complex arrays.Массив shape доступен только для чтения пользователем.
-
Py_ssize_t *strides¶
Массив
Py_ssize_t
длиныndim
, содержащий количество байт, которые нужно пропустить, чтобы перейти к новому элементу в каждом измерении.Значения шага могут быть любыми целыми числами. Для обычных массивов шаг обычно положительный, но пользователь ДОЛЖЕН уметь обращаться с регистром
strides[n] <= 0
. Дополнительную информацию смотрите в разделе complex arrays.Массив strides доступен только для чтения пользователем.
-
Py_ssize_t *suboffsets¶
Массив
Py_ssize_t
длинойndim
. Еслиsuboffsets[n] >= 0
, то значения, хранящиеся в n-м измерении, являются указателями, а значение подстановки определяет, сколько байт нужно добавить к каждому указателю после удаления ссылки. Отрицательное значение дополнительного смещения указывает на то, что смещение ссылок не должно происходить (перемещение в непрерывном блоке памяти).Если все подстановки отрицательные (т.е. разыменование не требуется), то в этом поле должно быть
NULL
(значение по умолчанию).Этот тип представления массива используется библиотекой изображений Python (PIL). Дополнительную информацию о том, как получить доступ к элементам такого массива, смотрите в разделе complex arrays.
Этот тип представления массива используется библиотекой изображений Python (PIL). Дополнительную информацию о том, как получить доступ к элементам такого массива, смотрите в разделе .
-
void *internal¶
Это значение предназначено для внутреннего использования экспортирующим объектом. Например, экспортер может преобразовать его в целое число и использовать для хранения флагов, указывающих на то, должны ли массивы shape, strides и suboffset освобождаться при освобождении буфера. Потребитель НЕ ДОЛЖЕН изменять это значение.
-
void *buf¶
Это значение предназначено для внутреннего использования экспортирующим объектом. Например, экспортер может преобразовать его в целое число и использовать для хранения флагов, указывающих на то, должны ли массивы shape, strides и suboffset освобождаться при освобождении буфера. Потребитель НЕ ДОЛЖЕН изменять это значение.
-
PyBUF_MAX_NDIM¶
Максимальное количество измерений, отображаемых в памяти. Экспортеры ДОЛЖНЫ соблюдать это ограничение, пользователи многомерных буферов ДОЛЖНЫ иметь возможность обрабатывать до
PyBUF_MAX_NDIM
измерений. В настоящее время установлено значение 64.
Запрос буфера¶
Буферы обычно получаются путем отправки запроса на буферизацию экспортируемому объекту через PyObject_GetBuffer()
. Поскольку сложность логической структуры памяти может сильно различаться, пользователь использует аргумент flags для указания точного типа буфера, который он может обрабатывать.
Все поля Py_buffer
однозначно определяются типом запроса.
поля, не зависящие от запроса¶
Следующие поля не зависят от флагов и всегда должны быть заполнены правильными значениями: obj
, buf
, len
, itemsize
, ndim
.
только для чтения,¶
- PyBUF_WRITABLE¶
Управляет полем
readonly
. Если оно задано, экспортер ДОЛЖЕН предоставить буфер, доступный для записи, иначе он сообщит о сбое. В противном случае экспортер МОЖЕТ предоставить буфер, доступный только для чтения, или буфер, доступный для записи, но выбор ДОЛЖЕН быть согласован для всех пользователей.
PyBUF_WRITABLE
может быть присвоено любому из флагов в следующем разделе. Поскольку PyBUF_SIMPLE
определяется как 0, PyBUF_WRITABLE
может использоваться как отдельный флаг для запроса простого буфера, доступного для записи.
PyBUF_FORMAT
может быть присвоено любому из флагов, кроме PyBUF_SIMPLE
. Последнее уже подразумевает формат B
(байты без знака).
фигура, шаги, промежуточные отступления¶
фигура, шаги, промежуточные отступления
фигура, шаги, промежуточные отступления |
форма |
s |
s |
---|---|---|---|
|
s |
s |
если |
|
s |
s |
если |
|
s |
если |
если |
|
если |
если |
если |
запросы на непрерывность¶
C или Fortran contiguity может быть запрошен явно, как с информацией о шаге, так и без нее. Без информации о шаге буфер должен быть C-непрерывным.
фигура, шаги, промежуточные отступления |
форма |
s |
s |
продолжение |
---|---|---|---|---|
|
s |
s |
если |
C |
|
s |
s |
если |
F |
|
s |
s |
если |
C или F |
s |
если |
если |
C |
сложные запросы¶
Все возможные запросы полностью определяются некоторой комбинацией флагов в предыдущем разделе. Для удобства протокол buffer предоставляет часто используемые комбинации в виде отдельных флагов.
В следующей таблице U означает неопределенную непрерывность. Потребителю пришлось бы вызвать PyBuffer_IsContiguous()
, чтобы определить непрерывность.
фигура, шаги, промежуточные отступления |
форма |
s |
s |
продолжение |
только для чтения |
формат |
---|---|---|---|---|---|---|
|
s |
s |
если |
U |
0 |
s |
|
s |
s |
если |
U |
1 или 0 |
s |
|
s |
s |
если |
U |
0 |
s |
|
s |
s |
если |
U |
1 или 0 |
s |
|
s |
s |
если |
U |
0 |
если |
|
s |
s |
если |
U |
1 или 0 |
если |
|
s |
если |
если |
C |
0 |
если |
|
s |
если |
если |
C |
1 или 0 |
если |
Сложный¶
Сложный¶
:member:`~Py_buffer.itemsize`Сложны :member:`~Py_buffer.ndim` :member:`~Py_buffer.shape` :member:`~Py_buffer.strides` й
Если ndim == 0
, то ячейка памяти, на которую указывает buf
, интерпретируется как скаляр размера itemsize
. В этом случае оба значения : c:member:~Py_buffer.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 могут содержать указатели, которым необходимо следовать, чтобы перейти к следующему элементу в измерении. Например, обычный трехмерный C-массив char v[2][2][3]
также можно рассматривать как массив из 2 указателей на 2 двумерных массива: char (*v[2])[2][3]
. В представлении suboffsets эти два указателя могут быть встроены в начало buf
, указывая на два массива char x[2][3]
, которые могут быть расположены в любом месте памяти.
Вот функция, которая возвращает указатель на элемент в NDarray, на который указывает 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;
}