Буферный протокол¶
Некоторые объекты, доступные в 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, это может быть необработанный блок памяти для манипуляций перед передачей в библиотеку операционной системы, или она может быть использована для передачи структурированных данных в их родном формате в памяти.
В отличие от большинства типов данных, открываемых интерпретатором Python, буферы не являются указателями PyObject
, а скорее простыми структурами языка Си. Это позволяет создавать и копировать их очень просто. Когда требуется общая обертка вокруг буфера, можно создать объект memoryview.
Краткие инструкции по написанию экспортируемого объекта см. в Buffer Object Structures. Для получения буфера смотрите PyObject_GetBuffer()
.
-
type
Py_buffer
¶ -
void *
buf
¶ Указатель на начало логической структуры, описываемой полями буфера. Это может быть любое место в базовом физическом блоке памяти экспортера. Например, при отрицательном
strides
значение может указывать на конец блока памяти.Для массивов contiguous значение указывает на начало блока памяти.
-
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()
, вызываемое на не``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
требует особого внимания. Дополнительную информацию см. в разделе 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 *
Типы запросов буфера¶
Буферы обычно получают, посылая запрос буфера экспортирующему объекту через PyObject_GetBuffer()
. Поскольку сложность логической структуры памяти может сильно различаться, потребитель использует аргумент flags для указания точного типа буфера, с которым он может работать.
Все поля Py_buffer
однозначно определяются типом запроса.
поля, не зависящие от запроса¶
Следующие поля не зависят от флагов и всегда должны быть заполнены правильными значениями: obj
, buf
, len
, itemsize
, ndim
.
только для чтения, формат¶
PyBUF_WRITABLE
¶Управляет полем
readonly
. Если оно установлено, экспортер ДОЛЖЕН предоставить буфер, доступный для записи, иначе он сообщит об отказе. В противном случае экспортер МОЖЕТ предоставить либо буфер только для чтения, либо буфер для записи, но выбор должен быть последовательным для всех потребителей.
PyBUF_WRITABLE
может быть |“d к любому из флагов в следующем разделе. Поскольку PyBUF_SIMPLE
определен как 0, PyBUF_WRITABLE
можно использовать как отдельный флаг для запроса простого буфера с возможностью записи.
PyBUF_FORMAT
может быть |“d к любому из флагов, кроме PyBUF_SIMPLE
. Последний уже подразумевает формат B
(беззнаковые байты).
форма, полосы, поднаборы¶
Флаги, управляющие логической структурой памяти, перечислены в порядке убывания сложности. Обратите внимание, что каждый флаг содержит все биты нижележащих флагов.
Запрос |
форма |
страйды |
поднаборы |
---|---|---|---|
|
да |
да |
при необходимости |
|
да |
да |
NULL |
|
да |
NULL |
NULL |
|
NULL |
NULL |
NULL |
запросы на смежность¶
C или Fortran contiguity может быть запрошен явно, с информацией о страйде и без нее. Без информации о страйде буфер должен быть C-континуальным.
Запрос |
форма |
страйды |
поднаборы |
contig |
---|---|---|---|---|
|
да |
да |
NULL |
C |
|
да |
да |
NULL |
F |
|
да |
да |
NULL |
C или F |
да |
NULL |
NULL |
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;
}