Buffer Protocol¶
Python中可用的某些对象包装访问底层内存数组或缓冲区。这些对象包括内建bytes
和bytearray
以及一些扩展类型,例如array.array
。第三方库可以为特殊目的定义它们自己的类型,例如图像处理或数字分析。
虽然每个类型都有自己的语义,但是它们具有由可能大的内存缓冲区支持的共同特性。因此,在某些情况下,希望直接访问该缓冲器,而不需要中间复制。
Python以buffer protocol的形式在C级提供了这样的功能。此协议有两个方面:
- 在生产者端,类型可以导出“缓冲区接口”,其允许该类型的对象暴露关于它们的底层缓冲区的信息。此接口在Buffer Object Structures部分中描述;
- 在消费者方面,几种手段可用于获得指向对象的原始基础数据(例如方法参数)的指针。
简单对象(例如bytes
和bytearray
)以字节为单位显示其底层缓冲区。其他形式是可能的;例如,由array.array
暴露的元素可以是多字节值。
缓冲区接口的示例消费者是文件对象的write()
方法:可以通过缓冲区接口导出一系列字节的任何对象都可以写入文件。虽然write()
只需要对传递给它的对象的内部内容进行只读访问,但是其他方法如readinto()
需要对它们的内容论据。缓冲器接口允许对象选择性地允许或拒绝读写和只读缓冲器的导出。
缓冲器接口的消费者有两种方式来获取目标对象上的缓冲区:
- 使用正确的参数调用
PyObject_GetBuffer()
; - call
PyArg_ParseTuple()
(or one of its siblings) with one of they*
,w*
ors*
format codes.
在这两种情况下,当不再需要缓冲区时,必须调用PyBuffer_Release()
。否则可能导致各种问题,例如资源泄漏。
Buffer structure¶
缓冲结构(或简称“缓冲器”)作为将二进制数据从另一个对象暴露给Python程序员的方法是有用的。它们也可以用作零拷贝切片机制。使用它们引用一块内存块的能力,可以很容易地向Python程序员公开任何数据。存储器可以是C扩展中的大的,恒定的数组,其可以是用于在传递到操作系统库之前进行操作的原始存储器块,或者其可以用于以其本机的内存格式传递结构化数据。
与由Python解释器暴露的大多数数据类型相反,缓冲区不是PyObject
指针,而是简单的C结构。这允许它们被非常简单地创建和复制。当需要缓冲区周围的通用包装器时,可以创建memoryview对象。
有关如何编写导出对象的简短说明,请参见Buffer Object Structures。要获取缓冲区,请参阅PyObject_GetBuffer()
。
-
Py_buffer
¶ - void *
buf
¶ 指向由缓冲区字段描述的逻辑结构的开始的指针。这可以是导出器的底层物理存储器块内的任何位置。例如,对于负的
strides
,该值可能指向内存块的末尾。对于contiguous数组,该值指向内存块的开头。
- void *
obj
¶ 对导出对象的新引用。引用由使用者拥有,并由
PyBuffer_Release()
自动递减并设置为NULL。该字段等价于任何标准C-API函数的返回值。作为特殊情况,对于由
PyMemoryView_FromBuffer()
或PyBuffer_FillInfo()
包装的临时缓冲区,此字段为NULL t7 >。一般来说,导出对象不能使用此方案。
- Py_ssize_t
len
¶ product(shape) * itemsize
。对于连续数组,这是底层内存块的长度。对于非连续数组,它是逻辑结构在复制到连续表示时将具有的长度。访问
((char *)buf)[0] 上 到 *)buf)[len-1]
仅在缓冲区通过确保连续性的请求获得时有效。在大多数情况下,这样的请求将是PyBUF_SIMPLE
或PyBUF_WRITABLE
。
- int
readonly
¶ 指示缓冲区是否为只读。此字段由
PyBUF_WRITABLE
标志控制。
- Py_ssize_t
itemsize
¶ 单个元素的项大小(以字节为单位)。与
struct.calcsize()
的值调用非NULLformat
值相同。重要例外:如果消费者请求没有
PyBUF_FORMAT
标志的缓冲区,format
将设置为NULL,但itemsize
如果
shape
存在,则等于乘积(形状) * itemsize / t7> len
仍然保持,消费者可以使用itemsize
导航缓冲区。如果作为
PyBUF_SIMPLE
或PyBUF_WRITABLE
请求的结果,shape
为NULL,则消费者必须忽略itemsize
并假设itemsize == 1
。
- const char *
format
¶ 在
struct
模块样式语法中描述单个项目内容的NUL终止字符串。如果这是NULL,则假设为"B"
(无符号字节)。此字段由
PyBUF_FORMAT
标志控制。
- int
ndim
¶ 存储器表示为n维数组的维数。如果它为0,则
buf
指向表示标量的单个项。在这种情况下,shape
,strides
和suboffsets
必须为NULL。宏
PyBUF_MAX_NDIM
将最大维数限制为64。出口商必须遵守此限制,多维缓冲器的消费者应该能够处理多达PyBUF_MAX_NDIM
维度。
- Py_ssize_t *
shape
¶ 长度
ndim
的Py_ssize_t
的数组指示作为n维数组的存储器的形状。注意,shape [0] * ... * shape [ndim-1 ] * itemsize
必须等于len
。形状值限于
shape [n] > = 0
。需要特别注意情况shape [n] == 0
有关详细信息,请参见复杂数组。形状数组对于消费者是只读的。
- Py_ssize_t *
strides
¶ 长度为
ndim
的Py_ssize_t
数组,给出每个维度中要跳到新元素的字节数。步幅值可以是任何整数。对于规则数组,步长通常是正的,但消费者必须能够处理
strides [n] 0 t3 >
有关详细信息,请参见复杂数组。strides数组对于消费者是只读的。
- Py_ssize_t *
suboffsets
¶ 长度为
ndim
的Py_ssize_t
数组。如果subboffset [n] > = 0
,沿着第n维存储的值是指针, value指示在解除引用之后要添加到每个指针的字节数。一个负的subboff值表示不应该发生取消引用(跨接在连续的内存块中)。如果所有子步骤都是负的(即无需取消引用,则此字段必须为NULL(默认值)。
这种类型的数组表示由Python Imaging库(PIL)使用。有关如何访问此类数组元素的更多信息,请参见复杂数组。
suboffsets数组对于使用者是只读的。
- void *
internal
¶ 这是由导出对象在内部使用。例如,这可能被输出器重新转换为整数,并用于存储关于在缓冲区释放时是否必须释放shape,strides和subboffsets数组的标志。消费者不得更改此值。
- void *
Buffer request types¶
缓冲区通常通过PyObject_GetBuffer()
向导出对象发送缓冲请求来获得。由于存储器的逻辑结构的复杂性可能发生显着变化,因此消费者使用flags参数指定其可以处理的确切缓冲区类型。
所有Py_buffer
字段由请求类型明确定义。
request-independent fields¶
The following fields are not influenced by flags and must always be filled in with the correct values: obj
, buf
, len
, itemsize
, ndim
.
readonly, format¶
PyBUF_WRITABLE
可以转到下一节中的任何标志。由于PyBUF_SIMPLE
定义为0,因此PyBUF_WRITABLE
可用作独立标志来请求简单的可写缓冲区。
PyBUF_FORMAT
可以写入除PyBUF_SIMPLE
之外的任何标志。后者已暗示格式B
(无符号字节)。
shape, strides, suboffsets¶
控制存储器的逻辑结构的标志以复杂性的降序列出。注意每个标志包含它下面的标志的所有位。
请求 | 形状 | 步幅 | 子步 |
---|---|---|---|
| 是 | 是 | 如果需要的话 |
| 是 | 是 | 空值 |
| 是 | 空值 | 空值 |
| 空值 | 空值 | 空值 |
contiguity requests¶
C或Fortran contiguity,可以有和没有步幅信息。没有步幅信息,缓冲区必须是C连续的。
请求 | 形状 | 步幅 | 子步 | 重叠群 |
---|---|---|---|---|
| 是 | 是 | 空值 | C |
| 是 | 是 | 空值 | F |
| 是 | 是 | 空值 | C或F |
| 是 | 空值 | 空值 | C |
compound requests¶
所有可能的请求都由上一节中的标志的某种组合完全定义。为了方便起见,缓冲器协议提供经常使用的组合作为单个标志。
在下表中U代表未定义的邻接。消费者必须调用PyBuffer_IsContiguous()
来确定连续性。
请求 | 形状 | 步幅 | 子步 | 重叠群 | 只读 | 格式 |
---|---|---|---|---|---|---|
| 是 | 是 | 如果需要的话 | U | 0 | 是 |
| 是 | 是 | 如果需要的话 | U | 1或0 | 是 |
| 是 | 是 | 空值 | U | 0 | 是 |
| 是 | 是 | 空值 | U | 1或0 | 是 |
| 是 | 是 | 空值 | U | 0 | 空值 |
| 是 | 是 | 空值 | U | 1或0 | 空值 |
| 是 | 空值 | 空值 | C | 0 | 空值 |
| 是 | 空值 | 空值 | C | 1或0 | 空值 |
Complex arrays¶
NumPy-style: shape and strides¶
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-style: shape, strides and suboffsets¶
除了常规项,PIL样式数组可以包含必须遵循的指针才能到达维中的下一个元素。例如,常规三维C阵列char v [2] [2] [3]
2个指针到2个二维数组:char (* v [2])[2] [3]
。在子boffets表示中,这两个指针可以嵌入在buf
的开始,指向两个char x [2] [3] t5 >
可以位于内存中任何位置的数组。
这里是一个函数,它返回一个指针指向一个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;
}