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

Певні об’єкти, доступні в Python, обертають доступ до базового масиву пам’яті або буфера. До таких об’єктів належать вбудовані `bytes` і `bytearray`, а також деякі типи розширень, наприклад `array.array`. Бібліотеки сторонніх розробників можуть визначати власні типи для спеціальних цілей, таких як обробка зображень або числовий аналіз.

У той час як кожен із цих типів має власну семантику, вони поділяють спільну характеристику, що вони підтримуються можливо великим буфером пам’яті. Тоді в деяких ситуаціях бажано отримати доступ до цього буфера безпосередньо й без проміжного копіювання.

Python надає таку можливість на рівні C у формі протоколу буфера. Цей протокол має дві сторони:

- на стороні виробника тип може експортувати «інтерфейс буфера», який дозволяє об’єктам цього типу надавати інформацію про їхній базовий буфер. Цей інтерфейс описано в розділі Буферні об’єктні структури;

- на стороні споживача доступно кілька засобів для отримання вказівника на необроблені базові дані об’єкта (наприклад, параметр методу).

Прості об’єкти, такі як `bytes` і `bytearray`, надають свій базовий буфер у байт-орієнтованій формі. Можливі інші форми; наприклад, елементи, представлені `array.array`, можуть мати багатобайтові значення.

An example consumer of the buffer interface is the `write()` method of file objects: any object that can export a series of bytes through the buffer interface can be written to a file. While `write()` only needs read-only access to the internal contents of the object passed to it, other methods such as `readinto()` need write access to the contents of their argument. The buffer interface allows objects to selectively allow or reject exporting of read-write and read-only buffers.

Споживач інтерфейсу буфера може отримати буфер над цільовим об’єктом двома способами:

- викликати `PyObject_GetBuffer()` з правильними параметрами;

- викликати `PyArg_ParseTuple()` (або один із його братів і сестер) з одним із `y*`, `w*` або `s*` кодів формату.

В обох випадках `PyBuffer_Release()` потрібно викликати, коли буфер більше не потрібен. Якщо цього не зробити, це може призвести до різноманітних проблем, наприклад до витоку ресурсів.

## Буферна структура

Буферні структури (або просто «буфери») корисні як спосіб надати двійкові дані з іншого об’єкта програмісту Python. Їх також можна використовувати як механізм нарізки без копіювання. Використовуючи їх здатність посилатися на блок пам’яті, можна досить легко надати будь-які дані програмісту Python. Пам’ять може бути великим постійним масивом у розширенні C, це може бути необроблений блок пам’яті для маніпуляцій перед передачею в бібліотеку операційної системи, або її можна використовувати для передачі структурованих даних у їх рідному форматі в пам’яті. .

На відміну від більшості типів даних, наданих інтерпретатором Python, буфери не є покажчиками `PyObject`, а досить простими структурами C. Це дозволяє дуже просто створювати та копіювати їх. Якщо потрібна загальна обгортка навколо буфера, можна створити об’єкт memoryview.

Щоб отримати короткі інструкції щодо написання об’єкта експорту, перегляньте Структури об’єктів буфера. Щоб отримати буфер, перегляньте `PyObject_GetBuffer()`.

type `Py_buffer`
  void *`buf`

    Покажчик на початок логічної структури, описаної полями буфера. Це може бути будь-яке розташування в базовому блоці фізичної пам’яті експортера. Наприклад, з негативним `strides` значення може вказувати на кінець блоку пам’яті.

    Для масивів contiguous значення вказує на початок блоку пам’яті.

  PyObject *`obj`

    A new reference to the exporting object. The reference is owned by the consumer and automatically released (i.e. reference count decremented) and set to `NULL` by `PyBuffer_Release()`. The field is the equivalent of the return value of any standard C-API function.

    Як окремий випадок, для тимчасових буферів, які обернуті `PyMemoryView_FromBuffer()` або `PyBuffer_FillInfo()`, це поле має значення `NULL`. Загалом, експорт об’єктів НЕ ПОВИНЕН використовувати цю схему.

  Py_ssize_t `len`

    `product(shape) * itemsize`. Для безперервних масивів це довжина основного блоку пам’яті. Для несуміжних масивів це довжина, яку мала б логічна структура, якби її було скопійовано до безперервного представлення.

    Доступ до `((char *)buf)[0] до ((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`

    A NUL terminated string in `struct` module style syntax describing the contents of a single item. If this is `NULL`, `"B"` (unsigned bytes) is assumed.

    Це поле контролюється прапорцем `PyBUF_FORMAT`.

  int `ndim`

    The number of dimensions the memory represents as an n-dimensional array. If it is `0`, `buf` points to a single item representing a scalar. In this case, `shape`, `strides` and `suboffsets` MUST be `NULL`.

    The macro `PyBUF_MAX_NDIM` limits the maximum number of dimensions to 64. Exporters MUST respect this limit, consumers of multi-dimensional buffers SHOULD be able to handle up to `PyBUF_MAX_NDIM` dimensions.

  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`, що вказує кількість байтів, які потрібно пропустити, щоб перейти до нового елемента в кожному вимірі.

    Величина кроку може бути будь-яким цілим числом. Для звичайних масивів кроки зазвичай позитивні, але споживач ПОВИНЕН вміти впоратися з випадком `ступені[n] <= 0`. Див. complex arrays для отримання додаткової інформації.

    Масив strides доступний лише для читання для споживача.

  Py_ssize_t *`suboffsets`

    Масив `Py_ssize_t` довжини `ndim`. Якщо `suboffsets[n] >= 0`, значення, що зберігаються вздовж n-го виміру, є вказівниками, а значення suboffset визначає, скільки байтів потрібно додати до кожного покажчика після видалення посилань. Від’ємне значення субзміщення вказує на те, що не повинно відбуватися видалення посилань (переміщення в безперервному блоці пам’яті).

    Якщо всі підзміщення є від’ємними (тобто не потрібно знімати посилання), тоді це поле має бути `NULL` (значення за замовчуванням).

    Цей тип представлення масиву використовується бібліотекою зображень Python (PIL). Див. complex arrays для отримання додаткової інформації про доступ до елементів такого масиву.

    Масив suboffsets доступний лише для читання для споживача.

  void *`internal`

    Це для внутрішнього використання об’єктом експорту. Наприклад, це може бути перетворено як ціле число експортером і використано для зберігання прапорів про те, чи потрібно звільняти масиви форми, кроків і підзміщень, коли буфер звільняється. Споживач НЕ ПОВИНЕН змінювати це значення.

## Типи запитів на буфер

Буфери зазвичай отримують шляхом надсилання запиту на буфер до об’єкта експорту через `PyObject_GetBuffer()`. Оскільки складність логічної структури пам’яті може різко змінюватися, споживач використовує аргумент flags, щоб визначити точний тип буфера, який він може обробляти.

All `Py_buffer` fields are unambiguously defined by the request type.

### поля, незалежні від запиту

На наступні поля не впливають прапорці, і їх потрібно завжди заповнювати правильними значеннями: `obj`, `buf`, `len`, `itemsize`, `ndim`.

### тільки для читання, формат

> `PyBUF_WRITABLE`

>   Controls the `readonly` field. If set, the exporter MUST provide a writable buffer or else report failure. Otherwise, the exporter MAY provide either a read-only or writable buffer, but the choice MUST be consistent for all consumers.

> `PyBUF_FORMAT`

>   Керує полем `format`. Якщо встановлено, це поле ПОВИННО бути заповнене правильно. В іншому випадку це поле ПОВИННО мати значення `NULL`.

`PyBUF_WRITABLE` можна приєднати до будь-якого з прапорів у наступному розділі. Оскільки `PyBUF_SIMPLE` визначено як 0, `PyBUF_WRITABLE` можна використовувати як окремий прапор для запиту простого буфера для запису.

`PyBUF_FORMAT` can be |“d to any of the flags except `PyBUF_SIMPLE`. The latter already implies format `B` (unsigned bytes).

### форма, кроки, підзміщення

Прапори, які керують логічною структурою пам’яті, перераховані в порядку зменшення складності. Зауважте, що кожен прапор містить усі біти прапорів під ним.

запит | форму | кроками | підзміщення
--- | --- | --- | ---
`PyBUF_INDIRECT` | так | так | при необхідності
`PyBUF_STRIDES` | так | так | НУЛЬ
`PyBUF_ND` | так | НУЛЬ | НУЛЬ
`PyBUF_SIMPLE` | НУЛЬ | НУЛЬ | НУЛЬ

### запити суміжності

C або Fortran contiguity можна запитати явно, з інформацією про крок або без неї. Без інформації про кроки буфер має бути C-суміжним.

запит | форму | кроками | підзміщення | контиг
--- | --- | --- | --- | ---
`PyBUF_C_CONTIGUOUS` | так | так | НУЛЬ | C
`PyBUF_F_CONTIGUOUS` | так | так | НУЛЬ | Ф
`PyBUF_ANY_CONTIGUOUS` | так | так | НУЛЬ | C або F
`PyBUF_ND` | так | НУЛЬ | НУЛЬ | C

### складені запити

Усі можливі запити повністю визначені деякою комбінацією прапорів у попередньому розділі. Для зручності протокол буфера надає часто використовувані комбінації як окремі прапорці.

У наступній таблиці U означає невизначену суміжність. Споживач мав би викликати `PyBuffer_IsContiguous()`, щоб визначити суміжність.

запит | форму | кроками | підзміщення | контиг | лише для читання | формат
--- | --- | --- | --- | --- | --- | ---
`PyBUF_FULL` | так | так | при необхідності | U | 0 | так
`PyBUF_FULL_RO` | так | так | при необхідності | U | 1 або 0 | так
`PyBUF_RECORDS` | так | так | НУЛЬ | U | 0 | так
`PyBUF_RECORDS_RO` | так | так | НУЛЬ | U | 1 або 0 | так
`PyBUF_STRIDED` | так | так | НУЛЬ | U | 0 | НУЛЬ
`PyBUF_STRIDED_RO` | так | так | НУЛЬ | U | 1 або 0 | НУЛЬ
`PyBUF_CONTIG` | так | НУЛЬ | НУЛЬ | C | 0 | НУЛЬ
`PyBUF_CONTIG_RO` | так | НУЛЬ | НУЛЬ | C | 1 або 0 | НУЛЬ

## Складні масиви

### 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-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)

  Send a request to exporter to fill in view as specified by flags. If the exporter cannot provide a buffer of the exact type, it MUST raise `PyExc_BufferError`, set `view->obj` to `NULL` and return `-1`.

  У разі успіху заповніть view, встановіть `view->obj` на нове посилання на exporter і поверніть 0. У випадку зв’язаних постачальників буферів, які перенаправляють запити до одного об’єкта, `view-> obj` МОЖЕ посилатися на цей об’єкт замість exporter (Див. Структури об’єктів буфера).

  Успішні виклики `PyObject_GetBuffer()` повинні бути поєднані з викликами `PyBuffer_Release()`, подібно до `malloc()` і `free()`. Таким чином, після того, як споживач завершить роботу з буфером, `PyBuffer_Release()` потрібно викликати рівно один раз.

void `PyBuffer_Release`(Py_buffer *view)

  Release the buffer view and release the strong reference (i.e. decrement the reference count) to the view’s supporting object, `view->obj`. This function MUST be called when the buffer is no longer being used, otherwise reference leaks may occur.

  Виклик цієї функції в буфері, який не було отримано через `PyObject_GetBuffer()`, є помилкою.

Py_ssize_t `PyBuffer_SizeFromFormat`(const char *format)

  Return the implied `itemsize` from `format`. On error, raise an exception and return -1.

  Нове в версії 3.9.

int `PyBuffer_IsContiguous`(Py_buffer *view, char order)

  Повертає `1`, якщо пам’ять, визначена view, є стилем C (order це `'C''`) або стилем Fortran (order це `'F'`) contiguous або один (порядок це `'A'`). Інакше поверніть `0`. Ця функція завжди успішна.

void *`PyBuffer_GetPointer`(Py_buffer *view, Py_ssize_t *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 байтовими кроками contiguous (у стилі 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 із можливістю запису, встановленою відповідно до readonly. buf інтерпретується як послідовність байтів без знаку.

  Аргумент flags вказує на тип запиту. Ця функція завжди заповнює view, як зазначено прапорцями, якщо buf не призначено лише для читання і `PyBUF_WRITABLE` встановлено у flags.

  On success, set `view->obj` to a new reference to exporter and return 0. Otherwise, raise `PyExc_BufferError`, set `view->obj` to `NULL` and return `-1`;

  Якщо ця функція використовується як частина getbufferproc, exporter ПОВИНЕН бути встановлений на об’єкт експорту, а flags мають бути передані без змін. В іншому випадку exporter ПОВИНЕН бути `NULL`.