如何构造含非连续缓冲区的Python对象使PyBuffer_IsContiguous返回False
Python C API里的PyBuffer_IsContiguous函数用来验证对象的缓冲区是否连续。你尝试的几个示例都没得到预期结果,下面逐个分析原因,再给出可行的构造方法:
无效示例分析
示例1:字节切片拼接的bytearray
a = b"123" b = b"456" c = bytearray(a[0:1] + b[0:1]) # 即使id(b[0]) == id(c[1]),该对象仍返回连续缓冲区 # 谁执行了复制?原因是什么?有哪些规则?
这里的核心是切片拼接操作会触发内存复制:字节串的切片是原对象的视图,但+运算符拼接两个切片时,会创建一个新的连续字节序列,再把这个序列传给bytearray构造函数。最终bytearray的内存是连续分配的,所以PyBuffer_IsContiguous返回True。id(b[0]) == id(c[1])只是因为Python对单个字节对象做了缓存,和c的内存连续性无关。
示例2:NumPy转置数组
np.zeros((2, 3)).T # PyObject_GetBuffer失败,未返回跨步数组
NumPy转置后的数组是原数组的视图,本身是非连续的,但你调用PyObject_GetBuffer时大概率没传入正确的标志。默认情况下,该函数会尝试获取连续缓冲区,对于非连续的NumPy数组会直接失败。需要指定PyBUF_STRIDES或PyBUF_FULL_RO这类允许返回跨步缓冲区的标志,才能成功获取非连续的缓冲区。
示例3:带步长的memoryview切片
memoryview(b"123")[::2] # PyObject_GetBuffer失败,未返回跨步数组
和NumPy的情况类似,带步长的memoryview确实是非连续的,但PyObject_GetBuffer默认不允许返回跨步缓冲区。必须在调用时传入PyBUF_STRIDES标志,才能获取到非连续的缓冲区结构,此时PyBuffer_IsContiguous才会返回False。
可行的构造方法
1. 带步长的memoryview + 正确的缓冲区请求标志
在C API中调用PyObject_GetBuffer时,传入PyBUF_STRIDES | PyBUF_FULL_RO标志,以memoryview(b"123")[::2]为例,就能获取到非连续的缓冲区,PyBuffer_IsContiguous会返回False。
2. NumPy非连续数组 + 正确的缓冲区请求标志
创建转置或其他非连续的NumPy数组(比如np.zeros((2,3))[:, ::2]),然后在C API中用PyBUF_STRIDES标志请求缓冲区,就能得到非连续的内存布局,让PyBuffer_IsContiguous返回False。
3. 自定义实现缓冲协议的对象
自己编写一个Python扩展类型,实现缓冲协议的getbuffer方法,返回一个具有跨步(strides)的非连续缓冲区结构。这种方式可以完全控制缓冲区的布局,确保PyBuffer_IsContiguous返回False。
内容的提问来源于stack exchange,提问作者Criminal_Affair_At_SO

