如何不将npz文件加载到内存即可读取其元数据获取存储数组形状
不加载npz文件内的数组到内存获取shape的实现方法
原生NumPy没有提供直接读取npz内部数组shape的公开接口,不过我们可以基于npz的文件结构轻松实现这个需求。
首先澄清一个常见认知:你调用np.load('xxx.npz')的时候并不会把所有数组一次性加载到内存,返回的NpzFile对象默认是懒加载的,只有你访问my_arrays['arr1']的时候才会把对应数组读入内存,但即便如此,访问.shape的时候还是会加载整个数组,处理超大数组时还是会造成不必要的内存占用。
npz本质是标准的ZIP压缩包,里面的每一个NumPy数组对应一个独立的.npy文件,而.npy文件的头部就存储了数组的shape、dtype等元信息,我们只需要读取对应.npy文件的头部几十字节,不需要加载整个数组就能拿到shape信息。
基础工具函数实现
import zipfile from numpy.lib.format import read_magic, _read_array_header def get_npz_array_shape(npz_path, array_name): with zipfile.ZipFile(npz_path, 'r') as zf: # npz内的数组对应的文件名为 数组名.npy npy_filename = f"{array_name}.npy" with zf.open(npy_filename, 'r') as f: # 读取npy格式魔数验证文件合法性 magic = read_magic(f) # 读取npy头部信息,返回元组(shape, dtype, 是否为Fortran顺序存储) shape, dtype, fortran_order = _read_array_header(f, magic.version) return shape
调用方式示例:
print(get_npz_array_shape('tmp/my_arrays.npz', 'arr1'))
该方法只会读取对应npy文件的头部,不会加载整个数组到内存。
封装为属性访问形式(匹配你想要的my_arrays.arr1.shape效果)
你可以封装一个自定义读取类,实现和你预期一致的调用方式:
import zipfile from numpy.lib.format import read_magic, _read_array_header class LazyNpzReader: def __init__(self, npz_path): self._zf = zipfile.ZipFile(npz_path, 'r') self._arr_meta = {} # 初始化时预读所有数组的元信息,不需要加载数组本身 for fname in self._zf.namelist(): if fname.endswith('.npy'): arr_name = fname[:-4] with self._zf.open(fname, 'r') as f: magic = read_magic(f) shape, dtype, _ = _read_array_header(f, magic.version) self._arr_meta[arr_name] = {'shape': shape, 'dtype': dtype} def __getattr__(self, name): if name in self._arr_meta: # 返回仅携带元信息的虚拟对象 return type('ArrayMeta', (), self._arr_meta[name])() raise AttributeError(f"npz文件中不存在名为{name}的数组") def close(self): self._zf.close() # 支持上下文管理器调用 def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.close()
调用方式完全符合你的预期:
with LazyNpzReader('tmp/my_arrays.npz') as my_arrays: # 直接获取shape,不会加载整个数组 print(my_arrays.arr1.shape) # 也可以获取dtype等其他元信息 print(my_arrays.arr1.dtype)
注意事项
- 上述实现用到的
_read_array_header是NumPy的私有接口,跨大版本更新时可能有变动,如果你需要长期稳定的实现,可以直接按照NumPy公开的npy格式规范自行解析头部,逻辑非常简单:npy文件前6字节为固定魔数\x93NUMPY,接下来2字节为版本号,再接下来2字节为头部长度,之后的头部内容是类JSON的字典,直接解析即可拿到shape、dtype等信息。 - 如果你对内存占用的要求不高,只是不想一次性加载所有数组,用原生
np.load返回的NpzFile懒加载机制已经足够,它只会在你访问对应key的时候加载单个数组,不会占用额外内存。
内容的提问来源于stack exchange,提问作者Marc P
相关产品推荐
相关产品推荐

