Python3中的内存视图memoryview

memoryview 是Python 3 的一个内置类


memoryview 它能让用户在不复制内容的情况下操作同一个数组的不同切片。memoryview 的概念受到了 NumPy 的启发。内存视图其实是泛化和去数学化的 NumPy 数组。它让你在不需要复制内容的前提下,在数据结构之间共享内存。其中数据结构可以是任何形式,比如 PIL 图片、SQLite 数据库和 NumPy 的数组,等等。这个功能在处理大型数据集合的时候非常重要。

memoryview.cast 的概念跟数组模块类似,能用不同的方式读写同一块内存数据,而且内容字节不会随意移动。这听上去又跟 C 语言中类型转换的概念差不多。memoryview.cast 会把同一块内存里的内容打包成一个全新的 memoryview 对象给你。

示例,我们利用 memoryview 精准地修改了一个数组的某个字节,这个数组的元素是 16 位二进制整数。通过改变数组中的一个字节来更新数组里某个元素的值:

>>> numbers = array.array('h', [-2, -1, 0, 1, 2])
>>> memv = memoryview(numbers)
>>> len(memv)
5
>>> memv[0]
-2
>>> memv_oct = memv.cast('B')
>>> memv_oct.tolist()
[254, 255, 255, 255, 0, 0, 1, 0, 2, 0]
>>> memv_oct[5] = 4
>>> numbers
array('h', [-2, -1, 1024, 1, 2])

利用含有 5 个短整型有符号整数的数组(类型码是 ‘h’)创建一个 memoryview。
memv 里的 5 个元素跟数组里的没有区别。
创建一个 memv_oct,这一次是把 memv 里的内容转换成 ‘B’ 类型,也就是无符号字符。
以列表的形式查看 memv_oct 的内容。
把位于位置 5 的字节赋值成 4。
因为我们把占 2 个字节的整数的高位字节改成了 4,所以这个有符号整数的值就变成了 1024。

以上示例代码涉及知识点: 小端机器,低地址存低位。计算机保存的是补码。短整型2个字节。

memoryview 类不是用于创建或存储字节序列的,而是共享内存,让你访问其他二进制序列、打包的数组和缓冲中的数据切片,而无需复制字节序列,例如 Python Imaging Library(PIL)就是这样处理图像的。

使用缓冲类对象创建 bytes 或 bytearray 对象时,始终复制源对象中的字节序列。与之相反,memoryview 对象允许在二进制数据结构之间共享内存。如果想从二进制序列中提取结构化信息,struct 模块是重要的工具。利用 memoryview 和 struct 可以用来操作二进制序列。

使用 memoryview 和 struct 查看一个 GIF 图像的首部,提取一个 GIF 图像的宽度和高度。

示例代码:

>>> import struct
>>> fmt = '<3s3sHH' # 1
>>> with open('test.gif', 'rb') as fp:
... img = memoryview(fp.read()) # 2
>>> header = img[:10] # 3
>>> bytes(header) # 4
b'GIF89a\x92\x02\xee\x01'
>>> struct.unpack(fmt, header) # 5
(b'GIF', b'89a', 658, 494)
>>> del header # 6
>>> del img # 7

示例说明:
1、结构体的格式:< 是小字节序,3s3s 是两个 3 字节序列,HH 是两个 16 位二进制整数。
2、使用内存中的文件内容创建一个 memoryview 对象。
3、然后使用它的切片再创建一个 memoryview 对象;这里不会复制字节序列。
4、转换成字节序列,这只是为了显示;这里复制了 10 字节。
5、拆包 memoryview 对象,得到一个元组,包含类型、版本、宽度和 高度。
6、7、删除引用,释放 memoryview 实例所占的内存。

不同后缀的图片的头部信息格式不同,以上代码示例只是针对gif图片的处理。

参考《流畅的Python》memoryview相关内容。