Буферний протокол¶
Певні об’єкти, доступні в 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()
.
-
Py_buffer
¶ -
void *
buf
¶ Покажчик на початок логічної структури, описаної полями буфера. Це може бути будь-яке розташування в базовому блоці фізичної пам’яті експортера. Наприклад, з негативним
strides
значення може вказувати на кінець блоку пам’яті.Для масивів contiguous значення вказує на початок блоку пам’яті.
-
void *
obj
¶ A new reference to the exporting object. The reference is owned by the consumer and automatically decremented and set to
NULL
byPyBuffer_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 isNULL
,"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
andsuboffsets
MUST beNULL
.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 toPyBUF_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
¶ Це для внутрішнього використання об’єктом експорту. Наприклад, це може бути перетворено як ціле число експортером і використано для зберігання прапорів про те, чи потрібно звільняти масиви форми, кроків і підзміщень, коли буфер звільняється. Споживач НЕ ПОВИНЕН змінювати це значення.
-
void *
Типи запитів на буфер¶
Буфери зазвичай отримують шляхом надсилання запиту на буфер до об’єкта експорту через PyObject_GetBuffer()
. Оскільки складність логічної структури пам’яті може різко змінюватися, споживач використовує аргумент flags, щоб визначити точний тип буфера, який він може обробляти.
All Py_buffer
fields are unambiguously defined by the request
type.
поля, незалежні від запиту¶
На наступні поля не впливають прапорці, і їх потрібно завжди заповнювати правильними значеннями: obj
, buf
, len
, itemsize
, ndim
.
тільки для читання, формат¶
PyBUF_WRITABLE
можна приєднати до будь-якого з прапорів у наступному розділі. Оскільки PyBUF_SIMPLE
визначено як 0, PyBUF_WRITABLE
можна використовувати як окремий прапор для запиту простого буфера для запису.
PyBUF_FORMAT
можна приєднати до будь-якого з прапорів, крім PyBUF_SIMPLE
. Останнє вже передбачає формат B
(беззнакові байти).
форма, кроки, підзміщення¶
Прапори, які керують логічною структурою пам’яті, перераховані в порядку зменшення складності. Зауважте, що кожен прапор містить усі біти прапорів під ним.
запит |
форму |
кроками |
підзміщення |
---|---|---|---|
|
так |
так |
при необхідності |
|
так |
так |
НУЛЬ |
|
так |
НУЛЬ |
НУЛЬ |
|
НУЛЬ |
НУЛЬ |
НУЛЬ |
запити суміжності¶
C або Fortran contiguity можна запитати явно, з інформацією про крок або без неї. Без інформації про кроки буфер має бути C-суміжним.
запит |
форму |
кроками |
підзміщення |
контиг |
---|---|---|---|---|
|
так |
так |
НУЛЬ |
C |
|
так |
так |
НУЛЬ |
Ф |
|
так |
так |
НУЛЬ |
C або F |
так |
НУЛЬ |
НУЛЬ |
C |
складені запити¶
Усі можливі запити повністю визначені деякою комбінацією прапорів у попередньому розділі. Для зручності протокол буфера надає часто використовувані комбінації як окремі прапорці.
У наступній таблиці U означає невизначену суміжність. Споживач мав би викликати PyBuffer_IsContiguous()
, щоб визначити суміжність.
запит |
форму |
кроками |
підзміщення |
контиг |
лише для читання |
формат |
---|---|---|---|---|---|---|
|
так |
так |
при необхідності |
U |
0 |
так |
|
так |
так |
при необхідності |
U |
1 або 0 |
так |
|
так |
так |
НУЛЬ |
U |
0 |
так |
|
так |
так |
НУЛЬ |
U |
1 або 0 |
так |
|
так |
так |
НУЛЬ |
U |
0 |
НУЛЬ |
|
так |
так |
НУЛЬ |
U |
1 або 0 |
НУЛЬ |
|
так |
НУЛЬ |
НУЛЬ |
C |
0 |
НУЛЬ |
|
так |
НУЛЬ |
НУЛЬ |
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;
}