Буферный протокол
Некоторые объекты, доступные в Python, обеспечивают доступ к базовому массиву
памяти или buffer. К таким объектам относятся встроенные 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.В качестве особого случая для буферов temporary, которые заключены в
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
.
-
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
. См. Сложные Массивы для получения дополнительной информации.Массив strides доступен только для чтения для потребителя.
-
Py_ssize_t *
suboffsets
Массив
Py_ssize_t
длиныndim
. Еслиsuboffsets[n] >= 0
, значения, хранящиеся в n-м измерении, являются указателями, а значение подсмещения определяет, сколько байтов добавить к каждому указателю после разыменования. Отрицательное значение субсмещения указывает, что разыменования не должно происходить (переход в непрерывный блок памяти).Если все подсмещения отрицательны (т.е. разыменование не требуется), тогда это поле должно быть
NULL
(значение по Умолчанию).Данный тип представления массива используется библиотекой изображений Python (PIL). См. Сложные Массивы для получения дополнительной информации о том, как получить доступ к элементам массива.
Массив suboffsets доступен только для чтения для потребителя.
-
void *
internal
Это предназначено для внутреннего использования экспортирующим объектом. Например, это может быть преобразовано в целое число экспортером и использовано для хранения флагов о том, должны ли быть освобождены массивы shape, strides и suboffsets при освобождении буфера. Потребитель НЕ ДОЛЖЕН изменять это значение.
-
void *
Типы запросов буфера
Буферы обычно получаются путем отправки запроса на буфер экспортируемому
объекту через 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
может быть | «d для любого из флагов, кроме
PyBUF_SIMPLE
. Последнее уже подразумевает формат B
(беззнаковые
байты).
форма, шаги, субсмещения
Флаги, управляющие логической структурой памяти, перечислены в порядке убывания сложности. Обратите внимание, что каждый флаг содержит все биты флагов под ним.
Запрос | форма | шаги | субсмещения |
---|---|---|---|
|
да | да | если нужно |
|
да | да | NULL |
|
да | NULL | NULL |
|
NULL | NULL | NULL |
запросы на смежность
C или Fortran смежные могут быть запрошены явно, с информацией о шаге и без нее. Без информации о шаге буфер должен быть C-смежным.
Запрос | форма | шаги | субсмещения | континг |
---|---|---|---|---|
|
да | да | NULL | C |
|
да | да | NULL | F |
|
да | да | NULL | C или F |
c:macro:: PyBUF_ND | да | 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):
"""Убедитесь, что параметры представляют допустимый
массив в пределах аллоцированный памяти:
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: форма, шаги и частичные смещения
В дополнение к обычным элементам массивы в стиле PIL могут содержать указатели,
которым необходимо следовать, чтобы перейти к следующему элементу в измерении.
Например, обычный трехмерный C-массив char v[2][2][3]
также можно
рассматривать как массив из 2 указателей на 2 двумерных массива: char
(*v[2])[2][3]
. В представлении подсмещения эти два указателя могут быть
встроены в начало buf
, указывая на два массива char
x[2][3]
, которые могут быть расположены в любом месте памяти.
Далее приведена возвращающая указатель на элемент в массиве N-D функция, на который
указывает 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;
}
Функции, связанные с буфером
-
int
PyObject_CheckBuffer
(PyObject *obj) Возвращает
1
, если obj поддерживает интерфейс буфера, иначе0
. Если возвращается1
, это не гарантирует успехаPyObject_GetBuffer()
. Функция всегда успешна.
-
int
PyObject_GetBuffer
(PyObject *exporter, Py_buffer *view, int flags) Отправляет запрос на exporter, чтобы заполнить view, как указано в flags. Если экспортер не может предоставить буфер точного типа, он ДОЛЖЕН вызвать
PyExc_BufferError
, установитьview->obj
наNULL
и вернуть-1
.В случае успеха заполняет view, устанавливает
view->obj
на новую ссылку на exporter и возвращает 0. В случае связанных поставщиков буферов, которые перенаправляют запросы на один объект,view->obj
МОЖЕТ ссылаться на данный объект вместо exporter (см. Структуры Буферных объектов).Успешные вызовы на
PyObject_GetBuffer()
должны сочетаться с вызовами наPyBuffer_Release()
, аналогичноmalloc()
иfree()
. Таким образом, после того, как потребитель закончит работу с буфером,PyBuffer_Release()
должен быть вызван ровно один раз.
-
void
PyBuffer_Release
(Py_buffer *view) Освободить буфер view и уменьшите счётчик ссылок для
view->obj
. Эта функция ДОЛЖНА быть вызвана, когда буфер больше не используется, иначе могут возникнуть утечки ссылок.Ошибка вызова этой функции для буфера, который не был получен через
PyObject_GetBuffer()
.
-
Py_ssize_t
PyBuffer_SizeFromFormat
(const char *) Возвращает подразумеваемый
itemsize
изformat
. Функция ещё не реализована.
-
int
PyBuffer_IsContiguous
(Py_buffer *view, char order) Возвращает
1
, если память, определяемая view, является C-стилем (order —'C'
) или Fortran-стилем (order —'F'
) смежный или одним из них (order —'A'
). В противном случае возвращает0
. Функция всегда выполняется успешно.
-
void*
PyBuffer_GetPointer
(Py_buffer *view, Py_ssize_t *indices) Получить область памяти, на которую указывает indices внутри данного view. indices должен указывать на массив индексов
view->ndim
.
-
int
PyBuffer_FromContiguous
(Py_buffer *view, void *buf, Py_ssize_t len, char fort) Скопировать непрерывные байты len из buf в view. fort может быть
'C'
или'F'
(для заказа в стиле C или Fortran).0
возвращается в случае успеха,-1
— в случае ошибки.
-
int
PyBuffer_ToContiguous
(void *buf, Py_buffer *src, Py_ssize_t len, char order) Скопировать байты len из src в его непрерывное представление в buf. order может быть
'C'
,'F'
или'A'
(для порядка в стиле C, Fortran или одного из них).0
возвращается в случае успеха,-1
— в случае ошибки.Функция не работает, если len! = src->len.
-
void
PyBuffer_FillContiguousStrides
(int ndims, Py_ssize_t *shape, Py_ssize_t *strides, int itemsize, char order) Заполняет массив strides байтовыми шагами смежного массива (в стиле C, если order — это
'C'
, или в стиле Фортрана, если order — это'F'
) массива заданной формы с заданным количеством байтов на Элемент.
-
int
PyBuffer_FillInfo
(Py_buffer *view, PyObject *exporter, void *buf, Py_ssize_t len, int readonly, int flags) Обработка запросов буфера для экспортера, который хочет предоставить buf размера len с возможностью записи, установленной в соответствии с readonly. buf интерпретируется как последовательность байтов без знака.
Аргумент flags указывает тип запроса. Функция всегда заполняет view, как указано флагами, если только buf не был определён как доступный только для чтения, а
PyBUF_WRITABLE
не установлен в flags.В случае успеха устанавливает
view->obj
на новую ссылку на exporter и возвращает 0. В противном случае вызываетсяPyExc_BufferError
, устанавливаетview->obj
наNULL
и возвращает-1
;Если функция используется как часть getbufferproc, exporter ДОЛЖЕН быть установлен для объекта экспорта, а flags должен передаваться без изменений. В противном случае exporter ДОЛЖЕН быть
NULL
.