当前位置: 首页 > 图灵资讯 > 行业资讯> 如何利用Python的memoryview在不拷贝数据的情况下处理大数组?

如何利用Python的memoryview在不拷贝数据的情况下处理大数组?

来源:图灵python
时间: 2026-07-20 17:04:53
memoryview 用于高效切片、类型转换和传输二进制数据的零拷贝视图 C 扩展;适用于 bytes、bytearray、array.array、numpy.ndarray 不能用于支持缓冲区协议的对象 list 或 str;确保源对象的生命周期比源对象长 memoryview,而且写作操作只在源可写时生效。

memoryview 它是什么,什么时候应该用它?

memoryview 它不是数组或容器,而是现有的二进制数据(例如 bytesbytearrayarray.arraynumpy.ndarray)“零拷贝视图”。它允许你直接读写底层内存块,而不复制原始数据——这对大数组(GB 等级)至关重要。但是注意:memoryview 没有数据本身,原对象被销毁后,memoryview 失效(会抛) ValueError: memoryview has been closed)。

  • 适用场景:需要频繁切片、类型转换或传输 C 扩展(如 NumPy、Pillow、struct)只是不想触发深拷贝
  • 不适用场景:想要修改不可变物体(例如) bytes)的内容——memoryview 可读,但写会报 TypeError
  • 常见误用:普通 liststr 构造 memoryview → 直接报 TypeError: cannot make memory view from a list
如何安全地创建和使用? memoryview 处理大 array.array

最安全的起点是 array.array,支持可变缓冲区协议,跨平台兼容性好。使用 array.array('d', [...]) 创建双精度浮点数组后,直接传输给 memoryview

import array
data = array.array('d', [1.0, 2.0, 3.0, 4.0])
mv = memoryview(data)

这时 mvdata 共享同一块内存。你可以:

  • 不复制切片:mv[1:3] 返回新 memoryview,它仍然指向原始内存
  • 类型重解释(要求元素大小相同):mv.cast('B') 把 float64 数组转换为字节视图(8)×4=32 字节),mv.cast('I') 转为 uint32(只有当原型是整形的,对齐的时候才安全)
  • 写作必须确保目标可以写:data 可以写,所以 mv[0] = 99.0 会真实修改 data[0];但若用 memoryview(b'hello'),赋值会失败
和 NumPy 的 interaction:别直接传 ndarray.data

NumPy 数组的 .data 属性返回 memoryview,但这是只读的(除非 ndarray.flags.writeable = True)。更推荐用 np.asarray(mv)np.frombuffer(mv, dtype=...) 构造新数组(仍不复制):

立即学习“Python免费学习笔记(深入);

Python 3.14.2

Python 3.14.2是Python编程语言于2025年12月5日发布的稳定版本,属于3.14系列的第二次维护更新。该版本包含18个修复项目,重点解决多过程、数据和正则表达模块的回归问题,修复CVE-2025-12084等安全漏洞。这个版本标志着Python发展的一个重要里程碑,即自由线程模式(删除GIL)正式得到官方支持。

下载

import numpy as np
mv = memoryview(bytearray(range(1000)))
arr = np.frombuffer(mv, dtype=np.uint8)  # 共享内存,arr[0] = 255 同时改 mv[0]

但要注意:

  • np.frombuffer() 要求 memoryview 对应的 buffer 是连续的(mv.contiguousTrue),否则报 ValueError: buffer is not contiguous
  • NumPy 的 ndarray 默认启用写作保护,如果原始保护 memoryview 来自只读源(如 mmap 即使设置了文件) writeable=True 也会在写入的时候 segfault
  • 避免混合使用:np.array(mv) 会被迫复制-这是最常踩的坑
实际处理大文件时的典型流程

比如读一个 2GB 的二进制 float32 数据文件,逐块处理,不完全加载到内存:

with open('data.bin', 'rb') as f:
    mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
    mv = memoryview(mm)
    # 按每 100 万个 float32(4MB)切割处理
    for i in range(0, len(mv), 1000000 * 4):
        chunk_mv = mv[i:i + 1000000 * 4]
        arr = np.frombuffer(chunk_mv, dtype=np.float32)
        process(arr)  # 此时 arr 与文件内存共享,无副本

关键点:

  • 必须用 mmapbytearray 这种支持缓冲区协议的对象作为源头
  • 每次 frombuffer 显式指定必须显式指定 dtype,不能依赖 mv.format(可能只是 'B'
  • 处理后及时释放 mmap,否则,文件的句柄泄漏;memoryview 本身不需要手动 close,但是,当所附对象被销毁后,它将失效

真正困难的是控制生命周期——一旦你把它放在一边 memoryview 谁负责保存多个函数的原始? buffer,中间的某个地方很容易发生意外 del 掉源对象,然后下一行 crash。这比语法细节更值得花时间设计。