Буферный протокол¶
Некоторые объекты, доступные в Python, обеспечивают доступ к базовому массиву памяти или буферу. К таким объектам относятся встроенные bytes
и bytearray
, а также некоторые расширенные типы, например array.array
. Сторонние библиотеки могут определять собственные типы для специальных целей, таких как обработка изображений или численный анализ.
Хотя каждый из этих типов имеет свою семантику, их объединяет то, что они опираются на возможно большой буфер памяти. В некоторых ситуациях желательно обращаться к этому буферу напрямую, без промежуточного копирования.
Python предоставляет такую возможность на уровне C в виде протокола buffer protocol. У этого протокола есть две стороны:
со стороны производителя тип может экспортировать «интерфейс буфера», который позволяет объектам этого типа раскрывать информацию о лежащем в их основе буфере. Этот интерфейс описан в разделе Структуры буферных объектов;
На стороне потребителя доступно несколько средств для получения указателя на исходные данные объекта (например, параметр метода).
Простые объекты, такие как bytes
и bytearray
, отображают свой базовый буфер в байт-ориентированной форме. Возможны и другие формы; например, элементы, выставляемые array.array
, могут быть многобайтовыми значениями.
Примером использования буферного интерфейса является метод write()
для файловых объектов: любой объект, который может экспортировать серию байтов через буферный интерфейс, может быть записан в файл. В то время как метод write()
нуждается только в доступе на чтение к внутреннему содержимому переданного ему объекта, другие методы, такие как readinto()
, нуждаются в доступе на запись к содержимому своего аргумента. Интерфейс буфера позволяет объектам выборочно разрешать или запрещать экспорт буферов, предназначенных только для чтения и записи.
У потребителя интерфейса буфера есть два способа получить буфер над целевым объектом:
вызовите
PyObject_GetBuffer()
с нужными параметрами;вызовите
PyArg_ParseTuple()
(или одного из его братьев и сестер) с помощью одного изy*
,w*
илиs*
format codes.
В обоих случаях вызов PyBuffer_Release()
должен происходить, когда буфер больше не нужен. В противном случае это может привести к различным проблемам, таким как утечка ресурсов.
Структура буфера¶
Буферные структуры (или просто «буферы») полезны как способ передачи двоичных данных из другого объекта программисту Python. Их также можно использовать в качестве механизма нарезки с нулевым копированием. Используя их способность ссылаться на блок памяти, можно легко предоставить программисту Python любые данные. Это может быть большой постоянный массив в расширении языка C, необработанный блок памяти для манипуляций перед передачей в библиотеку операционной системы или структурированные данные в их родном формате in-memory.
В отличие от большинства типов данных, открываемых интерпретатором Python, буферы - это не указатели PyObject
, а простые структуры на языке C. Это позволяет создавать и копировать их очень просто. Когда требуется общая обертка вокруг буфера, можно создать объект memoryview.
Краткие инструкции по написанию экспортируемого объекта см. в разделе Buffer Object Structures. О получении буфера см. в разделе PyObject_GetBuffer()
.
-
type Py_buffer¶
- Часть Стабильный ABI (включая всех членов) с версии 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]
возможен только в том случае, если буфер был получен запросом, гарантирующим его непрерывность. В большинстве случаев таким запросом будет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¶
Строка с окончанием NULL в синтаксисе стиля модуля
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 для получения дополнительной информации.Массив форм доступен потребителю только для чтения.
-
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-ом измерении, являются указателями, а значение suboffset определяет, сколько байт нужно добавить к каждому указателю после отмены ссылки. Отрицательное значение suboffset указывает на то, что отмены ссылки не должно быть (перемещение в непрерывном блоке памяти).Если все подмножества отрицательны (т.е. отсылка не требуется), то это поле должно быть
NULL
(значение по умолчанию).Этот тип представления массива используется библиотекой Python Imaging Library (PIL). Дополнительную информацию о том, как получить доступ к элементам такого массива, см. в разделе complex arrays.
Массив suboffsets доступен только для чтения потребителю.
-
void *internal¶
Это значение предназначено для внутреннего использования экспортирующим объектом. Например, он может быть пересчитан экспортером как целое число и использоваться для хранения флагов о том, должны ли массивы shape, strides и suboffsets быть освобождены при освобождении буфера. Потребитель НЕ ДОЛЖЕН изменять это значение.
-
void *buf¶
Константы:
-
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
(беззнаковые байты).
форма, полосы, подмножества¶
Флаги, управляющие логической структурой памяти, перечислены в порядке убывания сложности. Обратите внимание, что каждый флаг содержит все биты флагов, расположенных ниже него.
запросы на смежность¶
C или Fortran contiguity может быть запрошен явно, с информацией о страйде и без нее. Без информации о строках буфер должен быть C-континуальным.
составные запросы¶
Все возможные запросы полностью определяются некоторой комбинацией флагов, приведенных в предыдущем разделе. Для удобства в буферном протоколе часто используемые комбинации представлены в виде отдельных флагов.
В следующей таблице U означает неопределенную смежность. Потребитель должен вызвать PyBuffer_IsContiguous()
, чтобы определить смежность.
Запрос |
форма |
страйды |
поднаборы |
contig |
readonly |
формат |
---|---|---|---|---|---|---|
|
да |
да |
при необходимости |
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-стиль: форма и полосы¶
Логическая структура массивов в стиле 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;
}