Spec-Zone.ru › Python 3.12

Буферный протокол

Некоторые объекты 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().

type Py_buffer
Часть стабильного 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(), вызванной для значений format, отличных от NULL.

Важное исключение: если потребитель запрашивает буфер без флага 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

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

Константы:

PyBUF_MAX_NDIM

Максимальное число измерений, которое может представлять память. Экспортирующие объекты ДОЛЖНЫ соблюдать это ограничение, а потребители многомерных буферов ДОЛЖНЫ уметь обрабатывать до PyBUF_MAX_NDIM измерений. Текущее значение — 64.

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

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

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

Поля, не зависящие от запроса

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

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

PyBUF_WRITABLE

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

PyBUF_FORMAT

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

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

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

Форма, шаги, смещения

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

Запрос

форма

шаги

смещения

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

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):
    """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;
}

Функции, связанные с буферами

int PyObject_CheckBuffer(PyObject *obj)
Входит в стабильный ABI начиная с версии 3.11.

Возвращает 1, если obj поддерживает интерфейс буфера, иначе — 0. Возвращение 1 не гарантирует, что вызов PyObject_GetBuffer() завершится успешно. Эта функция всегда завершается успешно.

int PyObject_GetBuffer(PyObject *exporter, Py_buffer *view, int flags)
Входит в стабильный ABI начиная с версии 3.11.

Отправляет запрос exporter, чтобы заполнить view в соответствии с flags. Если поставщик не может предоставить буфер точно запрошенного типа, он ДОЛЖЕН вызвать исключение 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)
Входит в стабильный ABI начиная с версии 3.11.

Освобождает буфер view и снимает сильную ссылку (то есть уменьшает счётчик ссылок) на объект, поддерживающий представление, view->obj. Эту функцию НЕОБХОДИМО вызвать, когда буфер больше не используется, иначе возможна утечка ссылок.

Вызов этой функции для буфера, полученного не через PyObject_GetBuffer(), является ошибкой.

Py_ssize_t PyBuffer_SizeFromFormat(const char *format)
Входит в стабильный ABI начиная с версии 3.11.

Возвращает подразумеваемое значение itemsize на основе format. В случае ошибки вызывает исключение и возвращает -1.

Добавлено в версии 3.9.

int PyBuffer_IsContiguous(const Py_buffer *view, char order)
Входит в стабильный ABI начиная с версии 3.11.

Возвращает 1, если память, определённая объектом view, является непрерывной в стиле C (order равен 'C'), в стиле Fortran (order равен 'F') или в любом из этих стилей (order равен 'A'). В противном случае возвращает 0. Эта функция всегда завершается успешно.

void *PyBuffer_GetPointer(const Py_buffer *view, const Py_ssize_t *indices)
Входит в стабильный ABI начиная с версии 3.11.

Получает область памяти, на которую указывают индексы indices внутри заданного объекта view. indices должны указывать на массив из view->ndim индексов.

int PyBuffer_FromContiguous(const Py_buffer *view, const void *buf, Py_ssize_t len, char fort)
Входит в стабильный ABI начиная с версии 3.11.

Копирует len байт из непрерывной области buf в view. fort может быть равен 'C' или 'F' (для порядка в стиле C или Fortran). В случае успеха возвращается 0, в случае ошибки — -1.

int PyBuffer_ToContiguous(void *buf, const Py_buffer *src, Py_ssize_t len, char order)
Входит в стабильный ABI начиная с версии 3.11.

Копирует len байт из src в его непрерывное представление в buf. order может быть равен 'C', 'F' или 'A' (для порядка в стиле C, Fortran или любого из них). В случае успеха возвращается 0, в случае ошибки — -1.

Эта функция завершается ошибкой, если len != src->len.

int PyObject_CopyData(PyObject *dest, PyObject *src)
Входит в стабильный ABI начиная с версии 3.11.

Копирует данные из буфера src в буфер dest. Может преобразовывать буферы между стилями C и Fortran.

В случае успеха возвращается 0, в случае ошибки — -1.

void PyBuffer_FillContiguousStrides(int ndims, Py_ssize_t *shape, Py_ssize_t *strides, int itemsize, char order)
Входит в стабильный ABI начиная с версии 3.11.

Заполняет массив 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)
Входит в стабильный ABI начиная с версии 3.11.

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

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

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

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

© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/c-api/buffer.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API