Протокол буфера

Некоторые объекты, доступные в Python, имеют доступ к базовому массиву памяти или буферу. К таким объектам относятся встроенные bytes и bytearray, а также некоторые типы расширений, такие как array.array. Сторонние библиотеки могут определять свои собственные типы для специальных целей, таких как обработка изображений или числовой анализ.

Хотя каждый из этих типов имеет свою собственную семантику, они имеют общую характеристику, обеспечиваемую возможно большим буфером памяти. Затем в некоторых ситуациях желательно получить доступ к этому буферу непосредственно и без промежуточного копирования.

Python обеспечивает такое средство на уровне C в виде буферного протокола. Этот протокол содержит две стороны:

  • на стороне производителя тип может экспортировать «интерфейс буфера», который позволяет объектам этого типа предоставлять информацию об их базовом буфере. Этот интерфейс описан в разделе Буферные структуры объектов;
  • на стороне потребителя доступно несколько средств для получения указателя на исходные базовые данные объекта (например, параметр метода).

Простые объекты, такие как bytes и bytearray, предоставляют свой базовый буфер в байтоориентированной форме. Возможны и другие формы; например, элементы, предоставляемые array.array, могут быть многобайтовыми значениями.

Примером потребителя буферного интерфейса является write() метод файловых объектов: любой объект, который может экспортировать серию байтов через буферный интерфейс, может быть записан в файл. Хотя write() требует доступа только для чтения к внутреннему содержимому переданного ему объекта, другие методы, такие как readinto(), нуждаются в доступе для записи к содержимому их аргумента. Интерфейс буфера позволяет объектам выборочно разрешать или отклонять экспорт буферов только для чтения и записи.

Для потребителя интерфейса буфера существует два способа получения буфера над целевым объектом:

В обоих случаях необходимо вызывать 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() обратилось к не-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] H1583404716 shape[ndim-1] * itemsize ДОЛЖНО быть равно len.

Значения формы ограничены shape[n] >= 0. Дело shape[n] == 0 требует особого внимания. Дополнительную информацию см. в разделе сложные массивы.

Массив фигур доступен только для чтения потребителю.

Py_ssize_t *strides

Массив Py_ssize_t длины ndim указывающий количество байтов, пропускаемых для перехода к новому элементу в каждом измерении.

Stride значения может быть любым целым числом. Для обычных массивов шаги обычно бывают положительными, но потребитель ДОЛЖЕН уметь обрабатывать кейс strides[n] <= 0. Дополнительную информацию см. в разделе сложные массивы.

Массив stades доступен только для чтения потребителю.

Py_ssize_t *suboffsets

Массив Py_ssize_t длиной ndim. Если suboffsets[n] >= 0, значения, хранящиеся вдоль n-го измерения, являются указателями, а значение субсмещений определяет количество байтов, добавляемых к каждому указателю после отмены ссылки. Отрицательное значение субсчета указывает на то, что отмена ссылок не должна происходить (чередование в смежном блоке памяти).

Если все субсмещения отрицательны (то есть отмена ссылок не требуется), то это поле должно быть NULL (значение по умолчанию).

Этот тип представления множества - используемый библиотекой отображения Python (PIL). Дополнительные сведения о доступе к элементам такого массива см. в разделе сложные массивы.

Массив субсмещений доступен только для чтения потребителю.

void *internal

Он предназначен для внутреннего использования экспортирующим объектом. Например, он может быть повторно приведен экспортером в виде целого числа и используемый для хранения флагов о том, должны ли быть освобождены массивы формы, шагов и субсмещений при освобождении буфера. Потребитель НЕ ДОЛЖЕН изменять это значение.

Типы запросов буфера

Буферы обычно получаются путем отправки запроса буфера экспортирующему объекту через PyObject_GetBuffer(). Поскольку сложность логической структуры памяти может сильно изменяться, потребитель использует аргумент flags для указания точного типа буфера, который он может обрабатывать.

Все Py_buffer поля однозначно определяются типом запроса.

независимые от запроса поля

Следующие поля не зависят от flags и всегда должны быть заполнены правильным значениями: obj, buf, len, itemsize, ndim.

формат только для чтения

PyBUF_WRITABLE

Управляет полем readonly. Если установлено, экспортер ДОЛЖЕН предоставить буфер для записи или отчет об ошибке. В противном случае экспортер МОЖЕТ предоставить буфер только для чтения или для записи, но выбор ДОЛЖЕН быть согласован для всех потребителей.

PyBUF_FORMAT

Управляет полем format. Если установлено, это поле ДОЛЖНО быть заполнено правильно. В противном случае это поле ДОЛЖНО быть NULL.

PyBUF_WRITABLE может быть |“d к любому из флагов в следующем разделе. Поскольку PyBUF_SIMPLE определяется как 0, PyBUF_WRITABLE может использоваться как автономный флаг для запроса простого буфера для записи.

PyBUF_FORMAT могут быть |“d к любому из флагов, кроме PyBUF_SIMPLE. Последнее уже подразумевает формат B (беззнаковые байты).

форма, шаги, субсмещение

Флаги, управляющие логической структурой памяти, перечислены в порядке убывания сложности. Обратите внимание, что каждый флаг содержит все биты флагов под ним.

Запрос форма шаги субсмещения
PyBUF_INDIRECT
да да если нужно
PyBUF_STRIDES
да да NULL
PyBUF_ND
да NULL NULL
PyBUF_SIMPLE
NULL NULL NULL

запросы на непрерывность

В C или Fortran смежность может быть явно запрошена, с информацией о шаге и без нее. Без информации о шаге буфер должен быть C-смежным.

Запрос форма шаги субсмещения континг
PyBUF_C_CONTIGUOUS
да да NULL C
PyBUF_F_CONTIGUOUS
да да NULL F
PyBUF_ANY_CONTIGUOUS
да да NULL C или F
c:macro:: PyBUF_ND да NULL NULL C

составные запросы

Все возможные запросы полностью определяются некоторым сочетанием флагов в предыдущем разделе. Для удобства буферный протокол предоставляет часто используемую комбинацию одиночных флагов.

В следующей таблице U обозначает неопределенную непрерывность. Потребителю придется вызвать PyBuffer_IsContiguous(), чтобы определить непрерывность.

Запрос форма шаги субсмещения континг только для чтения формат
PyBUF_FULL
да да если нужно U 0 да
PyBUF_FULL_RO
да да если нужно U 1 или 0 да
PyBUF_RECORDS
да да NULL U 0 да
PyBUF_RECORDS_RO
да да NULL U 1 или 0 да
PyBUF_STRIDED
да да NULL U 0 NULL
PyBUF_STRIDED_RO
да да NULL U 1 или 0 NULL
PyBUF_CONTIG
да NULL NULL C 0 NULL
PyBUF_CONTIG_RO
да 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' или Fortran-стиль, если order 'F') массива данной формы с заданным количеством байтов на элемент.

int PyBuffer_FillInfo(Py_buffer *view, PyObject *exporter, void *buf, Py_ssize_t len, int readonly, int flags)

Обработка запросов буфера для экспортера, который хочет предоставить buf размера len с возможностью записи, установленной в соответствии с только для чтения. buf интерпретируется как последовательность байтов без знака.

Аргумент flags указывает тип запроса. Эта функция всегда заполняет view, как указано флагами, если buf не обозначен как доступный только для чтения, а PyBUF_WRITABLE установлен в flags.

При успехе устанавливается view->obj новой ссылке на exporter и возвращает 0. В противном случае поднимается PyExc_BufferError, устанавливается view->obj на NULL и возвращает -1;

Если эта функция используется как часть getbufferproc, exporter ДОЛЖЕН быть установлен в объект экспорта, и flags должен быть передан неизмененным. В противном случае exporter ДОЛЖЕН быть NULL.