Протокол буферизации
Некоторые объекты в 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(), вызываемому для значений, не являющихсяNULLformat.Важное исключение: если потребитель запрашивает буфер без флага
PyBUF_FORMAT,formatбудет установлено вNULL, ноitemsizeпо-прежнему сохраняет значение для исходного формата.Если
shapeприсутствует, равенствоproduct(shape) * itemsize == lenсохраняется, и потребитель может использоватьitemsizeдля навигации по буферу.Если
shapeприсутствует в результате запроса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ограничено максимальное число измерений до 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-го измерения, являются указателями, и значение смещения определяет, сколько байтов нужно добавить к каждому указателю после разыменования. Значение отрицательного смещения указывает, что разыменование не должно выполняться (шаги по смежному блоку памяти).Если все смещения отрицательные (т.е. разыменования не требуются), то это поле должно быть
NULL(значение по умолчанию).Этот тип представления массива используется библиотекой Python Imaging Library (PIL). См. сложные массивы для получения дополнительной информации о доступе к элементам такого массива.
Массив смещений является только для чтения для потребителя.
-
void *internal -
Используется внутренне экспортирующим объектом. Например, это может быть переинтерпретировано как целое число экспортером и использовано для хранения флагов о том, нужно ли освобождать массивы shape, strides и suboffsets при освобождении буфера. Потребитель НЕ должен изменять это значение.
-
Типы запросов на буферы
Буферы обычно получаются путем отправки запроса на буфер объекту-экспортеру через 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 (беззнаковые байты).
массив, шаги, смещения
Флаги, которые управляют логической структурой памяти, перечислены в порядке убывания сложности. Обратите внимание, что каждый флаг содержит все биты флагов ниже его.
Запрос | массив | шаги | смещения |
|---|---|---|---|
| да | да | если необходимо |
| да | да | 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–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.8/c-api/buffer.html