缓冲协议¶
在python中可用的某些对象对底层内存数组进行wrap访问,或者 缓冲区 . 这些对象包括内置的 bytes
和 bytearray
和一些扩展类型,如 array.array
. 第三方库可以为特殊目的定义自己的类型,例如图像处理或数字分析。
虽然每种类型都有自己的语义,但它们都有一个共同的特点,即被可能很大的内存缓冲区支持。在某些情况下,需要直接访问缓冲区,而不需要中间复制。
python以 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
指针,但相当简单的C结构。这样就可以非常简单地创建和复制它们。当需要缓冲区周围的通用封装器时, 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
flag。
-
Py_ssize_t itemsize¶
单个元素的项大小(字节)。与的值相同
struct.calcsize()
在非空时调用``format
价值观。重要异常:如果消费者请求的缓冲区没有
PyBUF_FORMAT
旗帜,format
将被设置为NULL
但是itemsize
仍然具有原始格式的值。如果
shape
存在,平等product(shape) * itemsize == len
仍然保留,消费者可以使用itemsize
导航缓冲区。如果
shape
是NULL
由于PyBUF_SIMPLE
或APyBUF_WRITABLE
请求,消费者必须忽略itemsize
并假设itemsize == 1
.
-
const char *format¶
A NUL 终止字符串
struct
描述单个项内容的模块样式语法。如果这是NULL
,"B"
假定为(无符号字节)。此字段由
PyBUF_FORMAT
flag。
-
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 更多信息。跨步数组对消费者是只读的。
-
Py_ssize_t *suboffsets¶
一个数组
Py_ssize_t
长度的ndim
.如果suboffsets[n] >= 0
,沿第n个维度存储的值是指针,子偏移值指示在取消引用后要向每个指针添加多少字节。负的子偏移值表示不应进行反引用(在连续内存块中删除)。如果所有子偏移都为负(即不需要反引用),则此字段必须为
NULL
(默认值)。Python图像库(PIL)使用这种类型的数组表示。见 complex arrays 有关如何访问此类数组元素的详细信息。
子偏移集数组对使用者是只读的。
-
void *internal¶
这供导出对象内部使用。例如,导出器可以将其重新转换为整数,并用于存储有关释放缓冲区时是否必须释放形状、跨距和子偏移集数组的标志。使用者不得更改此值。
-
void *buf¶
缓冲区请求类型¶
缓冲区通常是通过向导出对象发送缓冲区请求 PyObject_GetBuffer()
. 由于内存逻辑结构的复杂性可能会发生巨大的变化,因此使用者使用 flags 参数来指定它可以处理的确切缓冲区类型。
所有 Py_buffer
字段由请求类型明确定义。
请求独立字段¶
以下字段不受 flags 必须始终填写正确的值: obj
, buf
, len
, itemsize
, ndim
.
只读,格式¶
PyBUF_WRITABLE
下一节中的任何标志都可以。自从 PyBUF_SIMPLE
定义为0, PyBUF_WRITABLE
可以用作独立标志来请求简单的可写缓冲区。
PyBUF_FORMAT
可以标记到除 PyBUF_SIMPLE
. 后者已经暗示了格式 B
(无符号字节)。
形状、步幅、子偏移¶
控制内存逻辑结构的标志按复杂性降低的顺序列出。注意,每个标记都包含它下面的所有标记位。
请求 |
形状 |
大步 |
子偏移 |
---|---|---|---|
|
对 |
对 |
如果需要 |
|
对 |
对 |
NULL |
|
对 |
NULL |
NULL |
|
NULL |
NULL |
NULL |
连续性请求¶
C或FORTRAN语言 contiguity 可以明确请求,包括或不包括步幅信息。如果没有步幅信息,缓冲区必须是C-连续的。
请求 |
形状 |
大步 |
子偏移 |
连续 |
---|---|---|---|---|
|
对 |
对 |
NULL |
C |
|
对 |
对 |
NULL |
F |
|
对 |
对 |
NULL |
C或F |
对 |
NULL |
NULL |
C |
复合请求¶
所有可能的请求都由前一节中的一些标志组合完全定义。为了方便起见,缓冲协议提供了常用的组合作为单个标志。
在下表中 U 表示未定义的连续性。消费者必须调用 PyBuffer_IsContiguous()
以确定连续性。
请求 |
形状 |
大步 |
子偏移 |
连续 |
只读 |
格式 |
---|---|---|---|---|---|---|
|
对 |
对 |
如果需要 |
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-style:形状和步伐¶
NumPy-style数组的逻辑结构由 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]
也可以看作是指向两个二维数组的两个指针的数组: char (*v[2])[2][3]
. 在子偏移集表示中,这两个指针可以嵌入到 buf
,指向两个 char x[2][3]
可以位于内存中任何位置的数组。
下面是一个函数,当存在非“NULL”跨步和子偏移时,该函数返回指向N维索引指向的N维数组中元素的指针:
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;
}