缓冲区和 Memoryview 对象

用 C 实现的 Python 对象可以导出一组称为“缓冲区接口”的函数。对象可以使用这些函数以原始的,面向字节的格式公开其数据。对象的 Client 端可以使用缓冲区接口直接访问对象数据,而无需先复制它。

支持缓冲区接口的对象的两个示例是字符串和数组。字符串对象以缓冲区接口面向字节的形式公开字符内容。数组只能pass旧式缓冲区接口公开其内容。此限制不适用于 Python 3,其中memoryview对象也可以从数组构造。数组元素可以是多字节值。

缓冲区接口的一个示例用户是文件对象的write()方法。可以pass缓冲区接口导出一系列字节的任何对象都可以写入文件。 PyArg_ParseTuple()有许多格式代码可对对象的缓冲区接口进行操作,从目标对象返回数据。

从 1.6 版开始,Python 一直提供 Python 级别的缓冲区对象和 C 级别的缓冲区 API,以便任何内置或使用定义的类型都可以公开其特性。但是,由于各种缺点,两者均已弃用,并已在 Python 3 中正式删除,以支持新的 C 级缓冲区 API 和名为memoryview的新 Python 级对象。

新的缓冲区 API 已回传到 Python 2.6,而memoryview对象已回传到 Python 2.7. 强烈建议使用它们而不是旧的 API,除非出于兼容性原因而被禁止这样做。

new-style Py_buffer 结构

  • Py_buffer

      • 无效* buf
      • 指向对象内存开始的指针。
  • Py_ssize_t len

    • 内存的总长度(以字节为单位)。
  • int readonly

    • 缓冲区是否为只读的指示器。
  • const char * format

    • struct模块样式语法中的* NULL 终止字符串,提供了可pass缓冲区使用的元素的内容。如果这是 NULL *,则假定"B"(无符号字节)。
  • int ndim

    • 存储器表示的维数为多维数组。如果为0,则stridessuboffsets必须为* NULL *。
  • Py_ssize_t * shape

    • Py_ssize_t的数组,长度为ndim的数组,将内存的形状表示为多维数组。注意((*shape)[0] * ... * (*shape)[ndims-1])*itemsize应该等于len
  • Py_ssize_t * strides

    • Py_ssize_t s 数组的长度为ndim的数组,给出在每个维中跳转到新元素所要跳过的字节数。
  • Py_ssize_t * suboffsets

    • Py_ssize_t的数组,长度为ndim。如果这些子偏移量数字大于或等于 0,则沿指示的维存储的值是一个指针,并且子偏移量值指示在取消引用后要添加到指针的字节数。负的子偏移值表示不应取消引用(跨连续内存块)。

如果所有子偏移量均为负(即不需要取消引用),则此字段必须为 NULL(默认值)。

这是一个函数,当同时存在非 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;
 }
  • Py_ssize_t itemsize

    • 这是用于共享内存的每个元素的项目大小(以字节为单位)的存储。它在技术上是不必要的,因为可以使用PyBuffer_SizeFromFormat()来获取它,但是 Export 商可以在不解析格式字符串的情况下知道此信息,并且有必要知道项的大小以正确理解步幅。因此,存储起来更加方便快捷。
  • 无效* internal

    • 供导出对象在内部使用。例如,导出器可能会将其重新 Broadcast 为整数,并用于存储有关释放缓冲区时是否必须释放形状,步幅和子偏移数组的标志。Consumer 切勿更改此值。

缓冲区相关Function

  • int PyObject_CheckBuffer(PyObject ** obj *)

    • 如果* obj *支持缓冲区接口,则返回1,否则返回0
  • int PyObject_GetBuffer(PyObject *obj Py_buffer view *,int * flags *)

    • 将* obj 导出到Py_buffer view 。这些参数绝不能为 NULL *。 * flags *参数是一个位字段,指示调用者准备处理哪种缓冲区,以及允许导出程序返回哪种缓冲区。缓冲区接口允许进行复杂的内存共享,但是某些调用者可能无法处理所有复杂性,但可能希望查看导出器是否允许它们对内存进行简单的查看。

一些 Export 商可能无法以所有可能的方式共享内存,并且可能需要引发错误以向某些 Consumer 发出 signal,表明某些事情是不可能的。这些错误应该是BufferError,除非确实有另一个错误导致了该问题。导出器可以使用标志信息来简化Py_buffer结构中非默认值的填充量,并且/或者如果对象不支持其内存的更简单视图,则会引发错误。

成功返回0,错误返回-1

下表为* flags 参数提供了可能的值。

FlagDescription
PyBUF_SIMPLE这是默认标志状态。返回的缓冲区可能有也可能没有可写内存。数据的格式将假定为无符号字节。这是一个“独立”标志常量。它永远不需要对其他人“”。如果导出器无法提供这种连续的字节缓冲区,则会引发错误。
PyBUF_WRITABLE返回的缓冲区必须可写。如果不可写,则引发错误。
PyBUF_STRIDES这意味着PyBUF_ND。返回的缓冲区必须提供步幅信息(即,步幅不能为 NULL)。当使用者可以处理跨步,不连续的数组时,将使用此方法。处理步幅自动假定您可以处理形状。如果无法跨步表示数据(即没有子偏移),则导出器可能会引发错误。
PyBUF_ND返回的缓冲区必须提供形状信息。内存将假定为 C 样式连续的(最后一个尺寸变化最快)。如果导出器无法提供这种连续的缓冲区,则可能会引发错误。如果未给出,则形状将为* NULL *。
PyBUF_C_CONTIGUOUS PyBUF_F_CONTIGUOUS PyBUF_ANY_CONTIGUOUS这些标志指示连续性返回的缓冲区必须分别是 C 连续的(最后一个维度变化最快),Fortran 连续的(第一个维度变化最快)或任意一个。所有这些标志均表示PyBUF_STRIDES,并确保跨步缓冲区信息结构将正确填写。
PyBUF_INDIRECT该标志指示返回的缓冲区必须具有子偏移信息(如果不需要子偏移,则可以为 NULL)。当使用者可以处理这些子偏移量隐含的间接数组引用时,可以使用此方法。这意味着PyBUF_STRIDES
PyBUF_FORMAT如果提供了此标志,则返回的缓冲区必须具有真实的格式信息。当 Consumer 要检查实际存储的是哪种“数据”时,将使用此方法。如果要求,Export 商应始终能够提供此信息。如果未显式请求格式,则格式必须以* NULL *(表示'B'或无符号字节)形式返回。
PyBUF_STRIDED这等效于(PyBUF_STRIDES | PyBUF_WRITABLE)
PyBUF_STRIDED_RO这等效于(PyBUF_STRIDES)
PyBUF_RECORDS这等效于(PyBUF_STRIDES | PyBUF_FORMAT | PyBUF_WRITABLE)
PyBUF_RECORDS_RO这等效于(PyBUF_STRIDES | PyBUF_FORMAT)
PyBUF_FULL这等效于(PyBUF_INDIRECT | PyBUF_FORMAT | PyBUF_WRITABLE)
PyBUF_FULL_RO这等效于(PyBUF_INDIRECT | PyBUF_FORMAT)
PyBUF_CONTIG这等效于(PyBUF_ND | PyBUF_WRITABLE)
PyBUF_CONTIG_RO这等效于(PyBUF_ND)

MemoryView objects

2.7 版的新Function。

memoryview对象将新的 C 级缓冲区接口公开为 Python 对象,然后可以像其他任何对象一样传递该对象。

  • PyObject * PyMemoryView_FromObject(PyObject ** obj *)

    • 从定义新缓冲区接口的对象创建一个 memoryview 对象。
  • PyObject * PyMemoryView_FromBuffer(Py_buffer *查看)

    • 创建一个包装给定 buffer-info 结构* view *的 memoryview 对象。然后,memoryview 对象拥有缓冲区,这意味着您不应该自己try释放它:它将在释放 memoryview 对象时释放。
  • PyObject * PyMemoryView_GetContiguous(PyObject ** obj *,int * buffertype *,char * order *)

    • 从定义缓冲区接口的对象向连续的内存块(在'C'或'F'ortran * order *)中创建一个 memoryview 对象。如果内存是连续的,则 memoryview 对象指向原始内存。否则,将进行复制,并且 memoryview 指向新的字节对象。
  • int PyMemoryView_Check(PyObject ** obj *)

    • 如果对象* obj *是 memoryview 对象,则返回 true。当前不允许创建memoryview的子类。
  • Py_buffer * PyMemoryView_GET_BUFFER(PyObject ** obj *)

    • 返回指向给定对象包装的 buffer-info 结构的指针。对象**必须是一个 memoryview 实例;此宏不会检查其类型,您必须自己执行此操作,否则可能会崩溃。

旧式缓冲区对象

有关旧缓冲区接口的更多信息,请参见缓冲区对象结构部分,位于PyBufferProcs的描述下。

bufferobject.hHeaders(由Python.h包含)中定义了一个“缓冲区对象”。这些对象在 Python 编程级别上看起来与字符串对象非常相似:它们支持切片,索引,串联和其他一些标准的字符串操作。但是,它们的数据可以来自以下两个来源之一:来自一个内存块,或来自另一个导出缓冲区接口的对象。

缓冲区对象非常有用,可以将数据从另一个对象的缓冲区接口公开给 Python 程序员。它们也可以用作零拷贝切片机制。利用它们引用内存块的能力,可以很容易地将任何数据公开给 Python 程序员。内存可以是 C 扩展中的大型恒定数组,也可以是原始内存块,以便在传递给 os 库之前进行操作,或者可以用于以本机内存形式传递结构化数据。

  • PyBufferObject

    • PyObject的此子类型表示缓冲区对象。
  • PyTypeObject PyBuffer_Type

    • PyTypeObject的实例,表示 Python 缓冲区类型;它与 Python 层中的buffertypes.BufferType是同Pair象。 。
  • int Py_END_OF_BUFFER

  • int PyBuffer_Check(PyObject ** p *)

  • PyObject * PyBuffer_FromObject(PyObject ** base *,Py_ssize_t * offset *,Py_ssize_t * size *)

    • 返回值:新参考.

返回一个新的只读缓冲区对象。如果* base 不支持只读缓冲区协议或不完全提供一个缓冲区段,则产生TypeError;如果 offset 小于零,则产生ValueError。缓冲区将保存对 base 对象的引用,缓冲区的内容将指向 base 对象的缓冲区接口,从位置 offset 开始,并扩展为 size 字节。如果 size Py_END_OF_BUFFER,则新缓冲区的内容将扩展为 base *对象导出的缓冲区数据的长度。

在版本 2.5 中进行了更改:此函数将int类型用于* offset size *。这可能需要更改您的代码以正确支持 64 位系统。

  • PyObject * PyBuffer_FromReadWriteObject(PyObject ** base *,Py_ssize_t * offset *,Py_ssize_t * size *)
    • 返回值:新参考.

返回一个新的可写缓冲区对象。参数和异常与PyBuffer_FromObject()相似。如果* base *对象未导出可写缓冲区协议,则引发TypeError

在版本 2.5 中进行了更改:此函数将int类型用于* offset size *。这可能需要更改您的代码以正确支持 64 位系统。

  • PyObject * PyBuffer_FromMemory(void ** ptr *,Py_ssize_t * size *)
    • 返回值:新参考.

返回一个新的只读缓冲区对象,该对象从内存中的指定位置读取并具有指定的大小。调用者负责确保在返回的缓冲区对象存在时,不释放作为* ptr 传入的内存缓冲区。如果 size 小于零,则加ValueError。注意Py_END_OF_BUFFER可能传递给 size *参数。在这种情况下,将引发ValueError

在版本 2.5 中进行了更改:此Function为* size *使用了int类型。这可能需要更改您的代码以正确支持 64 位系统。

  • PyObject * PyBuffer_FromReadWriteMemory(void ** ptr *,Py_ssize_t * size *)
    • 返回值:新参考.

PyBuffer_FromMemory()相似,但是返回的缓冲区是可写的。

在版本 2.5 中进行了更改:此Function为* size *使用了int类型。这可能需要更改您的代码以正确支持 64 位系统。

  • PyObject * PyBuffer_New(Py_ssize_t * size *)
    • 返回值:新参考.

返回一个新的可写缓冲区对象,该对象保持其自己的* size 字节的内存缓冲区。如果 size *不是零或正数,则返回ValueError。请注意,内存缓冲区(由PyObject_AsWriteBuffer()返回)未明确对齐。

在版本 2.5 中进行了更改:此Function为* size *使用了int类型。这可能需要更改您的代码以正确支持 64 位系统。