memoryview是已有bytes等对象的只读内存视图,不复制数据,适用于高频读取、切片及跨函数传递大块二进制数据;创建时需传入支持缓冲协议的对象,且生命周期不得长于原对象。

memoryview 是什么,什么时候该用它
它不是新容器,而是已有 bytes、bytearray、array.array 等对象的「只读视图」——不复制数据,直接操作底层内存。适合高频读取、切片、跨函数传递大块二进制数据的场景,比如解析网络包、处理图像像素、序列化/反序列化中间层。
别在小字符串("hello")或临时 bytes(10) 上硬套 memoryview:开销反而比直接拷贝高;它真正省的是「避免重复分配 + memcpy」。
怎么创建和安全使用 memoryview
必须传入支持缓冲协议(buffer protocol)的对象,常见合法输入:bytes、bytearray、array.array('B')、numpy.ndarray(需 dtype 为字节型)。传 str 会直接报 TypeError: memoryview: a bytes-like object is required, not 'str'。
关键限制:视图生命周期不能长于原对象。一旦原 bytearray 被 del 或重赋值,对应 memoryview 可能读到脏数据或触发 ValueError: underlying buffer is not writable(如果尝试写)。
立即学习“Python免费学习笔记(深入)”;
- 安全做法:确保原对象在
memoryview使用期间始终存活,不被 gc 回收 - 避免:把
memoryview存进全局缓存,却让原bytearray在函数返回后就销毁 - 检查是否有效:用
.nbytes或.shape属性访问前先确认对象没被释放(无直接 API,靠逻辑约束)
切片、索引和零拷贝传输的实际表现
对 memoryview 切片(如 mv[100:200])仍是零拷贝,返回新 memoryview,共享同一块内存。但注意:它不会自动处理越界——mv[1000:] 返回空视图,不报错;而 mv[1000] 才会抛 IndexError。
传给 C 扩展或 socket.send() 这类接受 buffer 协议的接口时,memoryview 能直接用,无需转 bytes。但传给要求明确 bytes 类型的函数(如 hashlib.update())会失败,得显式调用 bytes(mv) —— 这一步就触发拷贝了。
- socket.send(mv) ✅ 零拷贝
- hashlib.md5().update(mv) ❌ 报 TypeError,必须写成
.update(bytes(mv)) - struct.unpack_from('<I', mv, offset=0) ✅ 支持,且比
bytes版本快 10%~20%(实测 1MB 数据)
和 bytes/bytearray 的性能差异与兼容陷阱
创建 memoryview 本身很快(O(1)),但它的很多操作(如 .tobytes()、.hex())会强制拷贝。想测真实零拷贝收益,得看端到端链路:比如从磁盘读 bytearray → 构建 memoryview → 多次切片传给 C 函数 → 不转 bytes 直接丢弃,全程无拷贝。
兼容性上,Python 3.12+ 对 memoryview 的 .cast() 支持更稳,但旧版本对非字节类型(如 'H' ushort)cast 后的索引行为可能不一致。跨版本部署时,优先用 struct.unpack_from 替代 .cast('H')[i]。
最常被忽略的一点:memoryview 对象本身不可哈希,不能做 dict key;也不支持 += 拼接。需要拼接?老实用 bytearray。











