Python: 截断字节缓冲区时的零拷贝

1 投票
3 回答
2185 浏览
提问于 2025-04-18 14:34

这是一个关于Python的新手问题。

有没有办法在Python中从字节数组的开头去掉几个字节,并且不把内容复制到另一个内存位置?下面是我正在做的事情:

inbuffer = bytearray()
inbuffer.extend(someincomingbytedata)
x = inbuffer[0:10]
del inbuffer[0:10]

我需要保留被去掉的字节(用x来表示),并对它进行一些操作。

那么,x会指向和inbuffer[0]相同的内存位置吗?还是上面代码的第三行会复制数据?另外,如果没有复制,最后一行的删除操作会不会也删除x所引用的数据?因为x仍然在引用这些数据,所以垃圾回收(GC)应该不会回收它,对吗?

编辑:

如果这不是正确的去掉字节缓冲区并返回去掉的字节而不复制的方法,还有没有其他类型可以安全地支持这样的操作?

3 个回答

0

检查起来非常简单:

>>> inbuffer = bytearray([1, 2, 3, 4, 5])
>>> x = inbuffer[0:2]
>>> print id(x) == id(inbuffer)
False

所以这不是同一个对象。

另外,你在问 x 指向 inbuffer[0] 的事情。你似乎有些误解。Python 中的数组和 C 语言中的数组工作方式不一样。inbuffer 的地址并不是 inbuffer[0] 的地址:

>>> inbuffer = bytearray([1, 2, 3, 4, 5])
>>> print id(inbuffer) == id(inbuffer[0])
False

这些其实是 C 语言数组的封装。

而且在 Python 中,所有东西都是对象。Python 会缓存所有小于等于 256 的整数(这就是 bytearray 的范围)。因此,唯一被复制的只是指针:

>>> inbuffer = bytearray([1, 2, 3, 4, 5])
>>> print id(inbuffer[0]) == id(1)
True
1

在你的例子中,x 会是一个新对象,它会保存 inbuffer[0:10] 的内容的一个“副本”。

如果你想要一个不复制内容的表示方式,你需要使用内存视图(memoryview,这个功能只有在 Python 3 中才有):

inbuffer_view = memoryview(inbuffer)
prefix = inbuffer_view[0:10]
suffix = inbuffer_view[10:]

现在,prefix 会指向 inbuffer 的前10个字节,而 suffix 会指向 inbuffer 剩下的内容。这两个对象内部都保持着对 inbuffer 的引用,所以你不需要特别去保持对 inbufferinbuffer_view 的引用。

需要注意的是,prefixsuffix 都是内存视图,而不是字节数组(bytearray)或字节(bytes)。你可以从它们创建字节和字节数组,但那样一来内容就会被复制。

内存视图可以传递给任何可以处理实现了缓冲协议的对象的函数。所以,比如说,你可以直接用 fh.write(suffix). 将它们写入文件。

0

你可以使用迭代器协议和 itertools.islice 来从你的 someincomingbytedata 可迭代对象中提取前10个值,然后把剩下的放到 inbuffer 里。这样做不会对所有字节使用相同的内存,但在避免不必要的复制方面,使用 bytearray 已经算是相当不错了:

import itertools

it = iter(someincomingbytedata)
x = bytearray(itertools.islice(it, 10)) # consume the first 10 bytes
inbuffer = bytearray(it)                # consume the rest

如果你确实需要一次性读取所有数据,然后又想高效地查看不同的部分而不进行复制,你可以考虑使用 numpy。如果你把数据加载到一个 numpy 数组中,之后你所做的任何切片操作都会直接在同一块内存上进行:

import numpy as np

inbuffer = np.array(someincomingdata, dtype=np.uint8)  # load data into an array of bytes
x = inbuffer[:10]  # grab a view of the first ten bytes, which does not require a copy
inbuffer = inbuffer[10:]  # change inbuffer to reference a slice; no copying here either

撰写回答