Протокол буферизации
Некоторые объекты в 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 -
Часть Стабильного ABI (включая все члены) с версии 3.11.
-
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.
-
char *format -
Строка с нулевым завершением в стиле модуля
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требует особого внимания. Подробную информацию см. в разделе «сложные массивы».Массив форм является неизменяемым для потребителя.
-
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 указывает, что разыменование не должно выполняться (шаговый доступ в непрерывном блоке памяти).Если все suboffsets отрицательные (т.е. разыменование не требуется), то это поле должно быть
NULL(значение по умолчанию).Этот тип представления массива используется библиотекой Python Imaging Library (PIL). Подробную информацию о доступе к элементам такого массива см. в разделе «сложные массивы».
Массив смещений является неизменяемым для потребителя.
-
void *internal -
Используется внутри экспортирующего объекта. Например, экспортер может преобразовать это в целое число и использовать для хранения флагов о том, нужно ли освобождать массивы shape, strides и suboffsets при освобождении буфера. Потребитель НЕ должен изменять это значение.
-
Константы:
-
PyBUF_MAX_NDIM -
Максимальное количество измерений, которые может представлять память. Экспортеры ДОЛЖНЫ соблюдать этот предел, потребители многомерных буферов ДОЛЖНЫ иметь возможность обрабатывать до
PyBUF_MAX_NDIMизмерений. В настоящее время установлено значение 64.
Типы запросов на буферы
Буферы обычно получаются путем отправки запроса на буфер объекту-экспортеру через PyObject_GetBuffer(). Поскольку сложность логической структуры памяти может существенно варьироваться, потребитель использует аргумент flags для указания точного типа буфера, который он может обработать.
Все поля Py_buffer однозначно определяются типом запроса.
Независимые от запроса поля
Следующие поля не зависят от flags и всегда должны быть заполнены правильными значениями: obj, buf, len, itemsize, ndim.
только чтение, формат
-
PyBUF_WRITABLE -
Управляет полем
readonly. Если установлено, экспортер ДОЛЖЕН предоставить доступный для записи буфер, иначе должен сообщить об ошибке. В противном случае экспортер МОЖЕТ предоставить буфер только для чтения или доступный для записи, но выбор ДОЛЖЕН быть согласованным для всех потребителей. Например, PyBUF_SIMPLE | PyBUF_WRITABLE может быть использован для запроса простого буфера с возможностью записи.
-
PyBUF_FORMAT -
Управляет полем
format. Если установлено, это поле ДОЛЖНО быть заполнено правильно. В противном случае это поле ДОЛЖНО бытьNULL.
PyBUF_WRITABLE может быть комбинировано с любым из флагов в следующем разделе. Поскольку PyBUF_SIMPLE определено как 0, PyBUF_WRITABLE может использоваться как самостоятельный флаг для запроса простого буфера с возможностью записи.
PyBUF_FORMAT должно быть комбинировано с любыми флагами, кроме PyBUF_SIMPLE, потому что последний уже подразумевает формат B (беззнаковые байты). PyBUF_FORMAT не может использоваться самостоятельно.
массив, шаги, смещения
Флаги, которые управляют логической структурой памяти, перечислены в порядке убывания сложности. Обратите внимание, что каждый флаг содержит все биты нижестоящих флагов.
Запрос | формат | шаги | смещения |
|---|---|---|---|
| да | да | при необходимости |
| да | да | NULL |
| да | NULL | NULL |
| NULL | NULL | NULL |
запросы на непрерывность
C или Fortran непрерывность можно явно запросить, с информацией о шагах и без неё. Без информации о шагах буфер должен быть C-непрерывным.
Запрос | формат | шаги | смещения | непрерывность |
|---|---|---|---|---|
| да | да | NULL | C |
| да | да | NULL | F |
| да | да | NULL | C или F |
да | NULL | NULL | C |
сложные запросы
Все возможные запросы полностью определяются некоторым сочетанием флагов в предыдущем разделе. Для удобства протокол буфера предоставляет часто используемые сочетания как отдельные флаги.
В следующей таблице U обозначает неопределённую непрерывность. Потребителю нужно будет вызвать PyBuffer_IsContiguous(), чтобы определить непрерывность.
Запрос | формат | шаги | смещения | непрерывность | только чтение | формат |
|---|---|---|---|---|---|---|
| да | да | при необходимости | 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;
}
© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/c-api/buffer.html