Содержание страницы
Протокол буфера¶Buffer Protocol
Некоторые объекты, доступные в Python, предоставляют доступ к лежащему в основе массиву памяти или буферу. К таким объектам относятся встроенные bytes и bytearray, а также некоторые расширяющие типы, например array.array. Сторонние библиотеки могут определять собственные типы для специальных целей, таких как обработка изображений или численный анализ.
Хотя каждый из этих типов имеет свою семантику, их объединяет общая характеристика: все они основаны на возможно большом буфере памяти. В некоторых ситуациях желательно получить прямой доступ к этому буферу без промежуточного копирования.
Python предоставляет такую возможность на уровне C и Python в виде протокола буфера. Этот протокол имеет две стороны:
На стороне производителя тип может экспортировать «буферный интерфейс», который позволяет объектам этого типа раскрывать информацию о своём базовом буфере. Этот интерфейс описан в разделе Структуры буферных объектов; для Python см. Эмуляция буферных типов.
На стороне потребителя доступны несколько способов получить указатель на необработанные базовые данные объекта (например, параметр метода). Для Python см.
memoryview.
Простые объекты, такие как bytes и bytearray, предоставляют свой базовый буфер в байт-ориентированной форме. Возможны и другие формы; например, элементы, предоставляемые array.array, могут быть многобайтовыми значениями.
Примером потребителя буферного интерфейса является метод write() файловых объектов: любой объект, который может экспортировать последовательность байтов через буферный интерфейс, может быть записан в файл. В то время как write() требуется только доступ на чтение к внутреннему содержимому переданного ему объекта, другие методы, такие как readinto(), требуют доступа на запись к содержимому своего аргумента. Буферный интерфейс позволяет объектам выборочно разрешать или запрещать экспорт буферов с возможностью чтения-записи и только для чтения.
Существует два способа для потребителя буферного интерфейса получить буфер над целевым объектом:
вызов
PyObject_GetBuffer()с правильными параметрами;вызов
PyArg_ParseTuple()(или одного из его аналогов) с одним изy*,w*илиs*кодов формата.
В обоих случаях PyBuffer_Release() должен быть вызван, когда буфер больше не нужен. Невыполнение этого требования может привести к различным проблемам, таким как утечка ресурсов.
Добавлено в версии 3.12: Теперь протокол буфера доступен в Python, см. Эмуляция буферных типов и memoryview.
Структура буфера¶Buffer structure
Структуры буферов (или просто «буферы») полезны как способ предоставления бинарных данных из другого объекта программисту на Python. Их также можно использовать как механизм среза с нулевым копированием. Благодаря возможности ссылаться на блок памяти, можно довольно легко предоставить любые данные программисту на Python. Память может быть большим константным массивом в C-расширении, необработанным блоком памяти для манипуляций перед передачей в библиотеку операционной системы или использоваться для передачи структурированных данных в их родном формате в памяти.
В отличие от большинства типов данных, предоставляемых интерпретатором Python, буферы не являются указателями PyObject, а представляют собой простые структуры C. Это позволяет очень просто создавать и копировать их. Когда нужна универсальная обёртка вокруг буфера, можно создать объект memoryview.
Краткие инструкции по написанию экспортирующего объекта см. в разделе Структуры буферных объектов. Для получения буфера см. PyObject_GetBuffer().
-
type Py_buffer¶
- Часть Stable 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¶
Строка с завершающим 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требует особого внимания. Дополнительную информацию см. в разделе сложные массивы.Массив формы доступен потребителю только для чтения.
-
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 при освобождении буфера. Потребитель НЕ ДОЛЖЕН изменять это значение.
-
void *buf¶
Константы:
-
PyBUF_MAX_NDIM¶
- Часть Stable ABI начиная с версии 3.11.
Максимальное количество измерений, представляемых памятью. Экспортёры ОБЯЗАНЫ соблюдать это ограничение, потребители многомерных буферов ДОЛЖНЫ уметь работать с количеством измерений до
PyBUF_MAX_NDIM. В настоящее время установлено 64.
Типы запросов буфера¶Buffer request types
Обычно буферы получают, отправляя запрос буфера экспортирующему
объекту через PyObject_GetBuffer(). Поскольку сложность логической
структуры памяти может сильно различаться, потребитель использует аргумент flags
для указания точного типа буфера, с которым он может работать.
Все поля Py_buffer однозначно определяются типом запроса.
поля, не зависящие от запроса¶request-independent fields
Следующие поля не зависят от flags и всегда должны заполняться
корректными значениями: obj, buf,
len, itemsize, ndim.
только для чтения, формат¶readonly, format
- PyBUF_WRITABLE¶
- Часть Stable ABI начиная с версии 3.11.
Управляет полем
readonly. Если установлен, экспортёр ОБЯЗАН предоставить буфер для записи, иначе сообщить об ошибке. В противном случае экспортёр МОЖЕТ предоставить буфер только для чтения или для записи, но выбор ДОЛЖЕН быть согласован для всех потребителей. Например, PyBUF_SIMPLE | PyBUF_WRITABLE может использоваться для запроса простого буфера для записи.
- PyBUF_WRITEABLE¶
Это псевдоним
PyBUF_WRITABLE.Мягкое устаревание начиная с версии 3.13.
- PyBUF_FORMAT¶
- Часть Stable ABI начиная с версии 3.11.
Управляет полем
format. Если установлен, это поле ДОЛЖНО быть заполнено корректно. В противном случае это поле ДОЛЖНО бытьNULL.
PyBUF_WRITABLE можно комбинировать (|) с любыми флагами из следующего раздела.
Поскольку PyBUF_SIMPLE определён как 0, PyBUF_WRITABLE
может использоваться как самостоятельный флаг для запроса простого буфера для записи.
PyBUF_FORMAT ДОЛЖЕН комбинироваться (|) с любыми флагами, кроме PyBUF_SIMPLE, поскольку
последний уже подразумевает формат B (unsigned bytes). PyBUF_FORMAT нельзя
использовать самостоятельно.
форма, шаги, подсмещения¶shape, strides, suboffsets
Флаги, управляющие логической структурой памяти, перечислены в порядке убывания сложности. Обратите внимание, что каждый флаг содержит все биты флагов, расположенных ниже него.
Запрос |
форма |
шаги |
подсмещения |
|---|---|---|---|
|
да |
да |
если необходимо |
|
да |
да |
NULL |
|
да |
NULL |
NULL |
|
NULL |
NULL |
NULL |
запросы непрерывности¶contiguity requests
Непрерывность по C или Fortran может быть явно запрошена, с информацией о шагах и без неё. Без информации о шагах буфер должен быть непрерывным по C.
Запрос |
форма |
шаги |
подсмещения |
непрерывность |
|---|---|---|---|---|
|
да |
да |
NULL |
C |
|
да |
да |
NULL |
F |
|
да |
да |
NULL |
C или F |
да |
NULL |
NULL |
C |
составные запросы¶compound requests
Все возможные запросы полностью определяются некоторой комбинацией флагов из предыдущего раздела. Для удобства протокол буфера предоставляет часто используемые комбинации в виде отдельных флагов.
В следующей таблице 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 |
Комплексные массивы¶Complex arrays
В стиле NumPy: форма и шаги¶NumPy-style: shape and 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):
"""Проверить, что параметры представляют допустимый массив в пределах
границ выделенной памяти:
char *mem: начало физического блока памяти
memlen: длина физического блока памяти
смещение: (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: форма, шаги и suboffsets¶PIL-style: shape, strides and suboffsets
В дополнение к обычным элементам массивы в стиле PIL могут содержать указатели,
по которым нужно перейти, чтобы получить следующий элемент в измерении.
Например, обычный трехмерный C-массив char v[2][2][3] можно
также рассматривать как массив из 2 указателей на 2 двумерных массива:
char (*v[2])[2][3]. В представлении suboffsets эти два указателя
могут быть встроены в начало buf, указывая
на два char x[2][3] массива, которые могут находиться в любом месте памяти.
Вот функция, которая возвращает указатель на элемент в N-мерном массиве,
на который указывает N-мерный индекс, при наличии как ненулевых NULL шагов,
так и suboffsets:
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;
}