在 Python 中可使用一些对象来包装对底层内存数组或称 缓冲 的访问。此类对象包括内置的 bytes 和 bytearray 以及一些如 array.array 这样的扩展类型。第三方库也可能会为了特殊的目的而定义它们自己的类型,例如用于图像处理和数值分析等。
虽然这些类型中的每一种都有自己的语义,但它们具有由可能较大的内存缓冲区支持的共同特征。 在某些情况下,希望直接访问该缓冲区而无需中间复制。
Python 以 缓冲协议 的形式在 C 层级上提供这样的功能。 此协议包括两个方面:
在生产者这一方面,该类型的协议可以导出一个“缓冲区接口”,允许公开它的底层缓冲区信息。该接口的描述信息在 缓冲区对象结构体 一节中;
在消费者一侧,有几种方法可用于获得指向对象的原始底层数据的指针(例如一个方法的形参)。
一些简单的对象例如 bytes 和 bytearray 会以面向字节的形式公开它们的底层缓冲区。 也可能会用其他形式;例如 array.array 所公开的元素可以是多字节值。
缓冲区接口的消费者的一个例子是文件对象的 write() 方法:任何可以输出为一系列字节流的对象都可以被写入文件。 然而 write() 只需要对传入对象内容的只读权限,其他的方法如 readinto() 需要对参数内容的写入权限。 缓冲区接口使用对象可以选择性地允许或拒绝读写或只读缓冲区的导出。
对于缓冲区接口的使用者而言,有两种方式来获取一个目的对象的缓冲:
使用正确的参数来调用 PyObject_GetBuffer() 函数;
调用 PyArg_ParseTuple() (或其同级对象之一) 并传入 y*, w* or s* 格式代码 中的一个。
在这两种情况下,当不再需要缓冲区时必须调用 PyBuffer_Release() 。如果此操作失败,可能会导致各种问题,例如资源泄漏。
缓冲区结构
缓冲区结构(或者简单地称为“buffers”)对于将二进制数据从另一个对象公开给 Python 程序员非常有用。它们还可以用作零拷贝切片机制。使用它们引用内存块的能力,可以很容易地将任何数据公开给 Python 程序员。内存可以是 C 扩展中的一个大的常量数组,也可以是在传递到操作系统库之前用于操作的原始内存块,或者可以用来传递本机内存格式的结构化数据。
与 Python 解释器公开的大多部数据类型不同,缓冲区不是 PyObject 指针而是简单的 C 结构。 这使得它们可以非常简单地创建和复制。 当需要为缓冲区加上泛型包装器时,可以创建一个 内存视图 对象。
有关如何编写并导出对象的简短说明,请参阅 缓冲区对象结构。 要获取缓冲区对象,请参阅 PyObject_GetBuffer()。
type Py_buffer
属于 稳定 ABI (包括所有成员) 自 3.11 版开始.
void *buf
指向由缓冲区字段描述的逻辑结构开始的指针。 这可以是导出程序底层物理内存块中的任何位置。 例如,使用负的 strides 值可能指向内存块的末尾。
对于 contiguous ,‘邻接’数组,值指向内存块的开头。
PyObject *obj
对导出对象的新引用。 该引用由消费方拥有,并由 PyBuffer_Release() 自动释放(即引用计数递减)并设置为 NULL。 该字段相当于任何标准 C-API 函数的返回值。
作为一种特殊情况,对于由 PyMemoryView_FromBuffer() 或 PyBuffer_FillInfo() 包装的 temporary 缓冲区,此字段为 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() 调用非 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
在 struct 模块样式语法中 NUL 字符串,描述单个项的内容。如果这是 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 形状数组中的值被限定在 shape[n] >= 0 。 shape[n] == 0 这一情形需要特别注意。更多信息请参阅 complex arrays 。
shape 数组对于使用者来说是只读的。
Py_ssize_t *strides
一个长度为 Py_ssize_t 的数组 ndim 给出要跳过的字节数以获取每个尺寸中的新元素。
Stride 步幅数组中的值可以为任何整数。对于常规数组,步幅通常为正数,但是使用者必须能够处理 strides[n] <= 0 的情况。更多信息请参阅 complex arrays 。
strides数组对用户来说是只读的。
Py_ssize_t *suboffsets
一个长度为 ndim 类型为 Py_ssize_t 的数组 。如果 suboffsets[n] >= 0,则第 n 维存储的是指针,suboffset 值决定了解除引用时要给指针增加多少字节的偏移。suboffset 为负值,则表示不应解除引用(在连续内存块中移动)。
如果所有子偏移均为负(即无需取消引用),则此字段必须为 NULL (默认值)。
Python Imaging Library (PIL) 中使用了这种类型的数组表达方式。请参阅 complex arrays 来了解如何从这样一个数组中访问元素。
suboffsets 数组对于使用者来说是只读的。
void *internal
供输出对象内部使用。比如可能被输出程序重组为一个整数,用于存储一个标志,标明在缓冲区释放时是否必须释放 shape、strides 和 suboffsets 数组。消费者程序 不得 修改该值。
常量:
PyBUF_MAX_NDIM
内存表示的最大维度数。 导出程序必须遵守这个限制,多维缓冲区的使用者应该能够处理最多 PyBUF_MAX_NDIM 个维度。 目前设置为 64。 |