首页
/ CPython 缓冲区协议(Buffer Protocol)完全解析:Py_buffer 结构、请求标志体系与零拷贝互操作原理

CPython 缓冲区协议(Buffer Protocol)完全解析:Py_buffer 结构、请求标志体系与零拷贝互操作原理

2026-09-06 11:37:37作者:尤辰城Agatha

本文以 CPython 官方 C-API 文档 Doc/c-api/buffer.rst 为核心骨架,完整讲解缓冲区协议的两侧模型、Py_buffer 结构的每个字段语义、全部请求类型(flags)的组合规则、NumPy/PIL 风格复杂数组的访问算法,以及 PyObject_GetBufferPyBuffer_Release 等配套 C 函数的正确用法,并结合 Include/pybuffer.hObjects/abstract.c 等源码给出实现层面的证据。读完后你将能够在 C 扩展中对任意支持缓冲区的 Python 对象做零拷贝读取/写入,并正确实现自己的 bf_getbuffer/bf_releasebuffer 槽位。

一、缓冲区协议:生产者与消费者的两侧模型

Python 中许多对象本质上是对一段底层内存数组(buffer)的封装,例如内置的 bytesbytearray,以及扩展类型 array.array;第三方库也会为图像处理、数值分析等场景定义自己的缓冲区类型。这些类型语义各异,但有一个共同点:背后都有一段可能很大的内存缓冲。在很多场景中(如写入文件、数值计算),希望直接访问这段内存而不做中间拷贝

缓冲区协议就是 CPython 在 C 层和 Python 层提供的这种设施,它有两面:

  • 生产者侧(producer):类型导出一个“缓冲区接口”(buffer interface),允许对象暴露其底层缓冲的信息。该接口由 PyBufferProcs 结构体描述,详见 Doc/c-api/typeobj.rst 的 Buffer Object Structures 一节;Python 层对应物是 memoryview,见 Doc/c-api/memoryview.rst
  • 消费者侧(consumer):通过 PyObject_GetBufferPyArg_ParseTupley*/w*/s* 格式代码,获取指向对象底层原始数据的指针(例如作为方法的参数)。Python 层的消费者则是 memoryview 对象。

bytesbytearray 这类简单对象以字节导向的形式暴露缓冲区;而 array.array 暴露的元素可以是多字节值,这正是协议中 itemsize/format 字段存在的意义。

协议的一个典型消费者是文件对象的 write 方法:任何能通过缓冲区接口导出一串字节的对象都可以写入文件。write 只需要传入对象的只读访问权,而 io.BufferedIOBase.readinto 之类的接口则需要可写访问权。缓冲区协议允许对象有选择地允许或拒绝导出只读/可写缓冲区。

消费者获取缓冲区有两条路径(原文档核心结论):

  1. 以合适的 flags 调用 PyObject_GetBuffer
  2. 调用 PyArg_ParseTuple(或其兄弟函数)并使用 y*w*s* 格式代码

两种方式都必须在使用完缓冲区后调用 PyBuffer_Release,否则可能造成资源泄漏、引用泄漏等一系列问题。

自 Python 3.12 起,缓冲区协议在 Python 层也可以直接使用(memoryview 暴露了更多底层访问能力,见 Doc/c-api/memoryview.rst)。

缓冲区结构体的双重身份

与解释器中大多数数据类型不同,缓冲区不是 PyObject 指针,而是普通的 C 结构体。这使得它们可以被极其简单地创建和拷贝;当需要一个围绕缓冲区的通用包装器时,就可以创建 memoryview 对象。

二、Py_buffer 结构体:逐字段语义

缓冲区(buffer)是向 Python 程序员暴露其他对象二进制数据的手段,也是零拷贝切片机制——利用其引用一块内存的能力,可以非常方便地暴露任意数据:C 扩展中的大型常量数组、传给操作系统库前待处理的原始内存块、以原生内存格式传递的结构化数据等。

CPython 中 Py_buffer 的完整定义位于 Include/pybuffer.h

typedef struct {
    void *buf;
    PyObject *obj;        /* owned reference */
    Py_ssize_t len;
    Py_ssize_t itemsize;  /* This is Py_ssize_t so it can be
                             pointed to by strides in simple case.*/
    int readonly;
    int ndim;
    char *format;
    Py_ssize_t *shape;
    Py_ssize_t *strides;
    Py_ssize_t *suboffsets;
    void *internal;
} Py_buffer;

源码注释特别说明:自 Python 3.11 起,该结构体同时属于 Limited API 和 stable ABI(abi3),其布局和尺寸不得更改,否则会破坏 ABI 兼容性(见 Include/pybuffer.h 的注释块)。逐字段语义如下(继承自 Doc/c-api/buffer.rst 的 Buffer structure 一节):

字段 类型 语义
buf void * 指向缓冲区字段所描述的逻辑结构的起始位置。它可以是导出者物理内存块中的任意位置:例如带有负 strides 时,它可能指向内存块的末尾。对连续数组,它指向内存块的开头。
obj PyObject * 对导出对象的新引用,所有权归消费者,由 PyBuffer_Release 自动释放(减引用计数并置 NULL)。对经 PyMemoryView_FromBufferPyBuffer_FillInfo 包装的临时缓冲区,该字段为 NULL(这是特例,导出对象不应模仿此方案)。
len Py_ssize_t 等于 product(shape) * itemsize。对连续数组即底层内存块长度;对非连续数组,是“若拷贝成连续表示后的长度”。只有当请求保证了连续性时(通常是 PyBUF_SIMPLEPyBUF_WRITABLE),访问 ((char *)buf)[0]((char *)buf)[len-1] 才是合法的。
readonly int 缓冲区是否只读的指示器,由 PyBUF_WRITABLE 标志控制。
itemsize Py_ssize_t 单个元素的字节数,等价于对非 NULLformat 调用 struct.calcsize 的结果。注意两个例外:① 消费者未请求 PyBUF_FORMATformatNULL,但 itemsize 仍保留原 format 的取值;② 若因 PyBUF_SIMPLE/PyBUF_WRITABLE 请求导致 shapeNULL,消费者必须忽略 itemsize 并假设 itemsize == 1。若 shape 存在,恒有 product(shape) * itemsize == len
format char * \0 结尾的、struct 模块风格语法描述单个元素内容的字符串。为 NULL 时默认视为 "B"(无符号字节)。由 PyBUF_FORMAT 标志控制。
ndim int 内存作为 n 维数组的维数。若为 0buf 指向一个大小为 itemsize标量,此时 shapestridessuboffsets 必须都是 NULL。最大维数由 PyBUF_MAX_NDIM 给出。
shape Py_ssize_t * 长度为 ndim 的数组,描述 n 维数组形状。必须满足 shape[0] * ... * shape[ndim-1] * itemsize == len。形状值限制为 shape[n] >= 0shape[n] == 0 的边界情形见下文“复杂数组”。对消费者只读
strides Py_ssize_t * 长度为 ndim 的数组,给出沿每个维度到达下一元素需要跳过的字节数。stride 可以是任意整数:常规数组通常为正,但消费者必须能处理 strides[n] <= 0。对消费者只读。
suboffsets Py_ssize_t * 长度为 ndim 的数组。若 suboffsets[n] >= 0,则第 n 维存储的是指针,取值表示解引用后还需额外加多少字节;负值表示不发生解引用(在连续内存块内直接 striding)。若全部为负(无需解引用),该字段必须为 NULL。这种表示由 Python Imaging Library(PIL)使用。对消费者只读。
internal void * 供导出对象内部使用。例如导出者可将其重解释为整数,记录 shape/strides/suboffsets 数组在释放缓冲区时是否需要 free消费者不得修改该值。

维度上限常量:

#define PyBUF_MAX_NDIM 64

(见 Include/pybuffer.h。)导出者必须遵守此上限;多维缓冲区的消费者应尽量能处理最多 PyBUF_MAX_NDIM 维。

三、缓冲区请求类型:flags 体系

缓冲区通常经由 PyObject_GetBuffer 向导出对象发送缓冲区请求而获得。由于底层内存逻辑结构的复杂度差异极大,消费者通过 flags 参数精确声明自己能够处理的缓冲区类型;每一个 Py_buffer 字段的定义都由请求类型唯一确定

3.1 与请求无关的字段

以下字段不受 flags 影响,导出者必须始终填入正确值:objbuflenitemsizendim

3.2 控制 readonly 与 format 的标志

标志 十六进制值 作用
PyBUF_SIMPLE 0 最简请求:shape/strides/suboffsets 全为 NULL,format 隐含为 "B"
PyBUF_WRITABLE 0x0001 控制 readonly 字段。若设置,导出者必须提供可写缓冲区,否则报失败。否则导出者可以提供只读或可写缓冲区,但对所有消费者必须一致。例如 PyBUF_SIMPLE | PyBUF_WRITABLE 即请求一个简单可写缓冲区。
PyBUF_WRITEABLE (别名) PyBUF_WRITABLE 的向后兼容别名,3.13 起被 soft-deprecated;且该别名不在 Limited API 中暴露(见 Include/pybuffer.h)。
PyBUF_FORMAT 0x0004 控制 format 字段:设置则必须正确填充,否则必须为 NULL

组合规则:

  • PyBUF_WRITABLE 可以与下一节任何标志按位或;由于 PyBUF_SIMPLE == 0PyBUF_WRITABLE 可单独使用来表示“简单可写缓冲区”。
  • PyBUF_FORMAT 必须与其他非 PyBUF_SIMPLE 标志组合使用——PyBUF_SIMPLE 已隐含 format "B"PyBUF_FORMAT 不能单独使用

3.3 控制逻辑结构(shape / strides / suboffsets)的标志

以下标志按复杂度从高到低排列,每个标志都包含其下方所有标志的位(这一点可在 Include/pybuffer.h 的宏定义中逐位验证):

请求 shape strides suboffsets
PyBUF_INDIRECT0x0100 | PyBUF_STRIDES 需要时
PyBUF_STRIDES0x0010 | PyBUF_ND NULL
PyBUF_ND0x0008 NULL NULL
PyBUF_SIMPLE0 NULL NULL NULL

3.4 连续性(contiguity)请求

可以显式请求 C 或 Fortran 连续性,带或不带 stride 信息。不带 stride 信息时,缓冲区必须是 C 连续的

请求 shape strides suboffsets 连续性
PyBUF_C_CONTIGUOUS0x0020 | PyBUF_STRIDES NULL C
PyBUF_F_CONTIGUOUS0x0040 | PyBUF_STRIDES NULL F
PyBUF_ANY_CONTIGUOUS0x0080 | PyBUF_STRIDES NULL C 或 F
PyBUF_ND NULL NULL C

3.5 复合请求(convenience flags)

任何请求都可以由前面各标志组合表达;协议为方便提供了常用组合。表中 U 表示未定义的连续性——消费者需要自行调用 PyBuffer_IsContiguous 判定:

请求 shape strides suboffsets contig readonly format
PyBUF_FULL 需要时 U 0
PyBUF_FULL_RO 需要时 U 1 或 0
PyBUF_RECORDS NULL U 0
PyBUF_RECORDS_RO NULL U 1 或 0
PyBUF_STRIDED NULL U 0 NULL
PyBUF_STRIDED_RO NULL U 1 或 0 NULL
PyBUF_CONTIG NULL NULL C 0 NULL
PyBUF_CONTIG_RO NULL NULL C 1 或 0 NULL

这些复合宏的确切展开见 Include/pybuffer.h,例如 PyBUF_RECORDS = PyBUF_STRIDES | PyBUF_WRITABLE | PyBUF_FORMATPyBUF_FULL = PyBUF_INDIRECT | PyBUF_WRITABLE | PyBUF_FORMAT

3.6 PyBUF_READ / PyBUF_WRITE:memoryview 专用标志

自 Python 3.11 起(同属 stable ABI,见 Doc/data/stable_abi.dat 第 39、47 行),公共头文件还定义了两个专用标志(Include/pybuffer.h):

#define PyBUF_READ  0x100
#define PyBUF_WRITE 0x200

它们是给 PyMemoryView_FromMemoryPyMemoryView_GetContiguous 等 memoryview 构造 API 使用的(语义分别是“请求只读缓冲区”与“请求可写缓冲区”,见 Doc/c-api/memoryview.rst)。不要把它们传给 PyObject_GetBuffer:从 Objects/abstract.c 的实现可以看到,PyObject_GetBuffer 有一条快速路径——flags != PyBUF_SIMPLEflags == PyBUF_READ || flags == PyBUF_WRITE 时会直接调用 PyErr_BadInternalCall() 返回 -1,即这两个值属于内部约定,直接调用即为内部错误。

四、复杂数组(Complex Arrays)

4.1 NumPy 风格:shape 与 strides

NumPy 风格数组的逻辑结构由 itemsizendimshapestrides 定义:

  • ndim == 0buf 指向的内存被解释为大小 itemsize标量,此时 shapestrides 均为 NULL
  • stridesNULL,数组按标准 n 维 C 数组解释。
  • 否则消费者必须按如下方式访问 n 维数组:
ptr = (char *)buf + indices[0] * strides[0] + ... + indices[n-1] * strides[n-1];
item = *((typeof(item) *)ptr);

注意 buf 可以指向实际内存块中的任意位置。导出者可以用下面这段官方参考实现来校验缓冲区的合法性(它检查 buf 相对物理内存块起点 mem 的偏移 offset = (char *)buf - mem 是否越界、是否与 itemsize 对齐、负 stride 情形下的访问边界是否仍在块内):

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

4.2 PIL 风格:shape、strides 与 suboffsets

PIL 风格数组除了常规元素外,还可以包含必须跟随的指针才能到达下一维的下一个元素。例如常规三维 C 数组 char v[2][2][3],也可以看作“2 个指向二维数组的指针”:char (*v[2])[2][3]。在 suboffsets 表示中,这两个指针可以嵌入在 buf 起始处,指向任意内存位置的 char x[2][3] 数组。

当 strides 和 suboffsets 都非 NULL 时,取 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;
}

CPython 内置的 PyBuffer_GetPointer 就是该算法的官方实现。

五、缓冲区配套 C 函数

以下函数声明见 Include/pybuffer.h,语义继承自 Doc/c-api/buffer.rst 的 Buffer-related functions 一节

int PyObject_CheckBuffer(PyObject *obj)

obj 支持缓冲区接口返回 1,否则返回 0。注意:返回 1 不保证 PyObject_GetBuffer 会成功(导出者仍可能因 flags 不满足而失败)。该函数本身永远成功。

int PyObject_GetBuffer(PyObject *exporter, Py_buffer *view, int flags)

exporter 发请求,按 flags 填充 view

  • 失败时:导出者必须抛出 BufferError,将 view->objNULL,返回 -1
  • 成功时:填充 view,将 view->obj 置为对 exporter新引用,返回 0。在链式缓冲区提供者的重定向场景下,view->obj 可能指向根对象而非 exporter(见 Doc/c-api/typeobj.rst)。

每次成功的 PyObject_GetBuffer 必须与恰好一次 PyBuffer_Release 配对,类似 malloc/free

Objects/abstract.c 可以看到其实现骨架:取出 Py_TYPE(obj)->tp_as_buffer;若 pb == NULL || pb->bf_getbuffer == NULL,抛出 TypeError: a bytes-like object is required, not '<type>' 并返回 -1;否则直接转调导出者的 bf_getbuffer(obj, view, flags)。CPython 内部大量使用它——例如 Objects/abstract.cas_read_bufferPyBUF_SIMPLE 取得 view.buf/view.len 后立即 PyBuffer_Release,这正是标准库 bytes.find 等接口能接受任意 bytes-like 对象的底层机制(Objects/bytes_methods.c 等处可见同样的调用模式)。

void PyBuffer_Release(Py_buffer *view)

释放缓冲区 view,并释放 view->obj 的强引用(减引用计数)。缓冲区不再使用时必须调用,否则会引用泄漏。对非 PyObject_GetBuffer 获得的缓冲区调用它是错误行为。

Py_ssize_t PyBuffer_SizeFromFormat(const char *format)

(3.9 起。)从 format 推出隐含的 itemsize;出错时抛异常并返回 -1

int PyBuffer_IsContiguous(const Py_buffer *view, char order)

view 定义的内存是 C 风格(order == 'C')、Fortran 风格('F')或任一种('A')连续的,返回 1,否则返回 0。该函数永远成功。其内部由 _IsCContiguous/_IsFortranContiguous 实现(见 Objects/abstract.c),核心判据是逐维校验 strides[i] == 累计 itemsize,并对 len == 0ndim <= 1strides == NULL(按定义即 C 连续)等边界做了特判。

void *PyBuffer_GetPointer(const Py_buffer *view, const Py_ssize_t *indices)

viewindices 指向的内存区域;indices 必须指向一个含 view->ndim 个索引的数组。即上文 get_item_pointer 的官方版本。

int PyBuffer_FromContiguous(const Py_buffer *view, const void *buf, Py_ssize_t len, char order)

把连续的 len 字节从 buf 拷入 vieworder 可为 'C''F''A'。成功返回 0,错误返回 -1

int PyBuffer_ToContiguous(void *buf, const Py_buffer *src, Py_ssize_t len, char order)

len 字节从 src 拷入其连续表示 buforder 同上。当 len != src->len 时失败。

int PyObject_CopyData(PyObject *dest, PyObject *src)

src 的数据拷贝到 dest 的缓冲区,可在 C 风格与 Fortran 风格缓冲区之间转换。成功返回 0,错误返回 -1。其实现(Objects/abstract.c)正是以 PyBUF_FULLdestPyBUF_FULL_ROsrc 的典型复合标志用法。

void PyBuffer_FillContiguousStrides(int ndims, Py_ssize_t *shape, Py_ssize_t *strides, int itemsize, char order)

按给定 shape 与每元素字节数,把 strides 填充为连续数组(order == 'F' 为 Fortran 风格,否则 C 风格)的字节步长。

int PyBuffer_FillInfo(Py_buffer *view, PyObject *exporter, void *buf, Py_ssize_t len, int readonly, int flags)

为“只想暴露一段长度为 len、以无符号字节序列解释的 buf(可写性由 readonly 决定)”的导出者处理缓冲区请求。它总是按 flags 正确填充 view,除非 buf 被标为只读而 flags 含 PyBUF_WRITABLE。成功时把 view->obj 置为 exporter 的新引用并返回 0;否则抛出 BufferError、置 view->obj = NULL 并返回 -1

使用约束:若作为 getbufferproc(见 Doc/c-api/typeobj.rst)的一部分使用,exporter 必须设为导出对象且 flags 必须原样透传;否则(即包装临时缓冲区的场景)exporter 必须为 NULL——这也解释了 Py_buffer.obj 字段中“临时缓冲区时为 NULL”的特例。

六、生产者侧实现:getbufferproc 与 releasebufferproc

Doc/c-api/typeobj.rst 定义了 PyBufferProcs 结构体及其两个函数指针,是自定义类型导出缓冲区的完整规范:

typedef int (*getbufferproc)(PyObject *, Py_buffer *, int);
typedef void (*releasebufferproc)(PyObject *, Py_buffer *);

(声明见 Include/pybuffer.h。)

bf_getbuffer 的强制步骤

签名:int (PyObject *exporter, Py_buffer *view, int flags);

除第 (3) 步外,实现必须按序执行:

  1. 检查请求能否满足;不能则抛出 BufferError、置 view->obj = NULL、返回 -1
  2. 填充被请求的字段;
  3. 递增内部导出计数(export counter);
  4. view->obj 设为 exporter 并递增其引用计数;
  5. 返回 0

free-threaded 构建下的线程安全要求(当前 CPython 源码文档中明确要求):导出计数递增必须原子;底层缓冲数据在所有导出存活期间必须保持有效且地址稳定;对支持扩容/重分配的对象(如 bytearray),必须在扩容前原子地检查导出计数,非零则抛出 BufferErrorbf_getbuffer 必须可被多线程并发调用。

链式/树状缓冲区提供者的两种方案

  • Re-export:树中每个成员都作为导出对象,把 view->obj 设为指向自己的新引用;
  • Redirect:请求重定向到树根对象,view->obj 成为根对象的新引用。

此外,Py_buffer 中所有指针指向的内存都属于导出者,必须保持有效直到没有任何消费者为止;formatshapestridessuboffsetsinternal 对消费者均为只读。

bf_releasebuffer 的规范

签名:void (PyObject *exporter, Py_buffer *view);

若无需释放资源,bf_releasebuffer 可以为 NULL。否则标准实现做(可选的)两步:(1) 递减导出计数;(2) 计数归零时释放与 view 相关的全部内存。free-threaded 构建下,计数递减与归零时的资源清理都必须原子,因为最后一次释放可能与其他线程的并发释放竞争,释放动作只能发生一次。

关键约束:

  • 导出者必须internal 字段跟踪缓冲区相关的资源——该字段保证保持不变,而消费者可能传入原缓冲区的副本作为 view 参数;
  • bf_releasebuffer 不得view->obj 减引用,因为那由 PyBuffer_Release 自动完成(此设计便于打破引用环)。

七、Python 层消费者:memoryview 与参数解析格式

memoryview:C 层缓冲区的 Python 包装

memoryview 对象把 C 层缓冲区接口暴露为可自由传递的 Python 对象(Doc/c-api/memoryview.rst)。相关 C API:

  • PyMemoryView_FromObject(PyObject *obj):从支持缓冲区接口的对象创建 memoryview;若对象支持可写导出,得到的 memoryview 可读可写,否则由导出者自行决定;
  • PyMemoryView_FromMemory(char *mem, Py_ssize_t size, int flags)(3.3 起):用裸内存块 mem 创建 memoryview,flagsPyBUF_READPyBUF_WRITE
  • PyMemoryView_FromBuffer(const Py_buffer *view):包装给定的 Py_buffer 结构;
  • PyMemoryView_GetContiguous(PyObject *obj, int buffertype, char order):取得指向连续内存块('C' 或 'F' 序)的 memoryview;内存本就连续时直接指向原内存,否则做拷贝并指向新的 bytes 对象;
  • PyMemoryView_CheckPyMemoryView_GET_BUFFERPyMemoryView_GET_BASE 等辅助接口(GET_BASE 对由 FromMemory/FromBuffer 创建的 memoryview 返回 NULL)。

PyArg_ParseTuple 的 s* / y* / w* 格式

Doc/c-api/arg.rst 明确了带星号的格式代码会填充一个 Py_buffer 结构体:

  • s*str 或 bytes-like 对象 → Py_buffer(其变体还允许 None、提供借用缓冲区);
  • y*:仅 bytes-like 对象(不接受 Unicode)→ Py_buffer
  • w*:读写的 bytes-like 对象 → Py_buffer

用这些格式代码得到的 Py_buffer,同样必须在使用后调用 PyBuffer_Release——Include/pybuffer.hPyBuffer_Release 的注释("Releases a Py_buffer obtained from getbuffer ParseTuple's 's*'")点明了这一点。

八、实践要点与常见陷阱清单

综合原文档与源码证据,使用缓冲区协议时最易踩的坑:

  1. 忘记 PyBuffer_Release:每次 PyObject_GetBuffer 成功或 s*/y*/w* 解析成功后,都必须恰好释放一次;PyBuffer_Release 同时负责减 view->obj 的引用,忘记调用既泄漏引用又会让导出者的 export counter 无法归零(对 bytearray 这类对象将永久阻断其扩容)。
  2. 误用 len:只有当请求保证连续性(PyBUF_SIMPLEPyBUF_WRITABLE 等)时,才能把 ((char *)buf)[0 .. len-1] 当作一块可读的连续内存;带 strides 的缓冲区中 len 只是“逻辑上连续化之后的长度”。
  3. 误读 itemsizePyBUF_SIMPLE/PyBUF_WRITABLE 请求下 shape == NULL 时必须假设 itemsize == 1,哪怕导出者内部元素是多字节的。
  4. 负 stride 与 shape[n] == 0:消费者必须能处理 strides[n] <= 0(此时 buf 可能指向块末尾附近)以及零维形状;合法性校验可参照上文的 verify_structure
  5. PyBUF_READ/PyBUF_WRITE 不是 PyObject_GetBuffer 的合法 flags:它们专供 memoryview 构造 API,传给 PyObject_GetBuffer 会触发内部错误(Objects/abstract.c)。
  6. 消费者不得修改 internalformatshapestridessuboffsets:它们归导出者所有且对消费者只读;自定义导出者应把“这些数组是否需要释放”之类的状态存进 internal
  7. 简单导出用 PyBuffer_FillInfo:只暴露一段连续字节内存的类型无需手写 flags 分派逻辑,但注意 getbufferproc 场景必须原样透传 exporterflags

参考路径速查

内容 路径
缓冲区协议主文档(本文骨架) Doc/c-api/buffer.rst
Py_buffer 结构与全部 flags 宏 Include/pybuffer.h
PyBufferProcs / getbuffer / releasebuffer 规范 Doc/c-api/typeobj.rst
memoryview C API 与 PyBUF_READ/PyBUF_WRITE Doc/c-api/memoryview.rst
PyObject_GetBuffer 实现与内部调用示例 Objects/abstract.c
s*/y*/w* 参数格式 Doc/c-api/arg.rst
stable ABI 中 PyBUF_READ/PyBUF_WRITE 条目 Doc/data/stable_abi.dat

以上结论均以当前仓库的文档与源码为准:Py_buffer 的布局与 flags 语义自 3.11 起属于 stable ABI,在 Limited API 环境下同样可用;PyBUF_READ/PyBUF_WRITE 的公共化及 memoryview 的 Python 层扩展则分别对应 3.11 与 3.12 的文档版本标注。

登录后查看全文
热门项目推荐
相关项目推荐