NumPy save vs savez:如何选择? (如果文章会对比的话) – wiki基地


NumPy save vs savez:如何选择?一份深度对比与应用指南

引言:数据持久化的重要性

在数据科学、机器学习、科学计算以及其他许多依赖于大规模数值计算的领域,NumPy 库因其高效的多维数组处理能力而成为事实上的标准。然而,数据的生命周期不仅仅局限于内存中的计算。为了实现数据的复用、模型的保存与加载、计算结果的分享或者避免重复耗时的预处理,我们需要将内存中的 NumPy 数组存储到磁盘上,并在需要时重新加载。这个过程被称为数据持久化(Data Persistence)。

NumPy 提供了多种内置的方法来实现数组的持久化,其中最常用和推荐的是 numpy.savenumpy.savez (以及 numpy.savez_compressed) 函数。它们都旨在以高效的二进制格式存储 NumPy 数组,从而保留数组的数据类型、形状和内容,避免了文本格式(如CSV)可能带来的精度损失和读写效率低下问题。

虽然 savesavez 都服务于数据持久化的目的,但它们之间存在着根本性的设计差异,这些差异决定了它们各自适用的场景。理解这些区别对于优化数据存储、提高程序效率和简化数据管理至关重要。

本文将深入探讨 numpy.savenumpy.savez 以及 numpy.savez_compressed 的工作原理、使用方法、优缺点以及适用场景。通过详细的对比分析,我们将帮助您在不同的数据存储需求下,明智地选择最合适的 NumPy 持久化工具。

NumPy 的二进制存储格式:.npy 与 .npz

在深入函数细节之前,了解一下 NumPy 使用的两种主要的二进制文件格式非常有帮助:

  1. .npy 格式: 这是 numpy.save 函数创建的标准格式。它专门用于存储单个 NumPy 数组。.npy 文件包含一个文件头,记录了数组的形状(shape)、数据类型(dtype)、字节顺序(endianness)以及其他元信息,紧接着是数组的原始二进制数据。这种格式非常紧凑和高效,可以直接映射到内存中进行读取(memory-mapping),对于加载大型单数组尤其有利。

  2. .npz 格式: 这是 numpy.saveznumpy.savez_compressed 函数创建的标准格式。它实际上是一个 ZIP 压缩包,但其内部包含的是一个或多个 .npy 文件。每个 .npy 文件对应着一个被保存的数组。.npz 格式允许我们将多个相关的 NumPy 数组打包到一个文件中,方便管理和分发。savez 创建的是未压缩的 ZIP 存档,而 savez_compressed 创建的是使用 zlib 压缩的 ZIP 存档,可以显著减小文件大小。

理解这两种底层格式是理解 savesavez 函数差异的关键。save 处理单个 .npy 文件,而 savezsavez_compressed 处理包含多个 .npy 文件的 .npz 压缩包。

深度解析 numpy.save

numpy.save 函数是 NumPy 中用于将单个数组以 .npy 格式保存到文件的基本工具。

基本语法:

python
numpy.save(file, arr, allow_pickle=True, fix_imports=True)

  • file: 文件名(字符串)或文件对象。如果提供文件名,会自动添加 .npy 扩展名(如果文件名没有指定扩展名的话)。
  • arr: 要保存的 NumPy 数组。
  • allow_pickle: (布尔值,默认为 True)是否允许使用 Python pickle 协议保存非 NumPy 对象数组。通常建议将其设置为 False,除非您确定需要保存包含任意 Python 对象的数组,并且理解潜在的安全风险(加载 pickle 文件可能执行恶意代码)。对于标准的数值 NumPy 数组,此参数通常不影响。
  • fix_imports: (布尔值,默认为 True)与 pickle 协议相关,帮助在不同 Python 版本之间加载旧的 pickle 文件。对于 .npy 格式的纯 NumPy 数组,通常不重要。

工作原理:

numpy.save 会创建一个 .npy 文件。它首先将数组的形状、数据类型、是否 Fortran 连续等信息写入文件头部,然后将数组的原始字节数据直接写入文件头部之后。整个过程非常直接和高效。

优点:

  1. 简单易用: 语法非常简洁,只需指定文件名和要保存的数组。
  2. 高效快速: 对于保存单个数组而言,save 的速度通常是最快的,因为它涉及到最少的开销——直接的二进制写入。
  3. 节省空间(对于单个数组): .npy 格式是为单个数组优化的,不包含压缩包的额外开销(如目录结构)。
  4. 支持内存映射(Memory-Mapping): 使用 numpy.load 加载 .npy 文件时,可以通过设置 mmap_mode 参数启用内存映射。这意味着数据不是一次性全部加载到内存,而是根据需要从文件中读取。这对于处理超出可用内存的大型数组非常有用。

缺点:

  1. 只能保存单个数组: 这是 save 最主要的限制。如果您有多个相关的数组需要保存,必须为每个数组调用一次 save,导致生成多个文件。这会使得文件管理变得复杂,尤其当数组数量很多时。

示例代码:

“`python
import numpy as np

创建一个 NumPy 数组

data_array = np.random.rand(100, 50)
print(f”要保存的数组形状: {data_array.shape}, 数据类型: {data_array.dtype}”)

使用 save 保存数组到文件

file_name_save = ‘single_array.npy’
np.save(file_name_save, data_array)
print(f”数组已保存到文件: {file_name_save}”)

加载保存的数组

loaded_array_save = np.load(file_name_save)
print(f”从文件 {file_name_save} 加载的数组形状: {loaded_array_save.shape}”)
print(f”数据是否一致: {np.array_equal(data_array, loaded_array_save)}”)

示例:保存到文件对象

with open(‘single_array_obj.npy’, ‘wb’) as f:

np.save(f, data_array)

print(“数组已保存到文件对象: single_array_obj.npy”)

loaded_array_obj = np.load(‘single_array_obj.npy’)

print(f”从文件对象加载的数组形状: {loaded_array_obj.shape}”)

print(f”数据是否一致: {np.array_equal(data_array, loaded_array_obj)}”)

示例:使用 mmap_mode 加载大型文件(假设文件很大)

loaded_array_mmap = np.load(file_name_save, mmap_mode=’r’)

print(f”使用 mmap_mode 加载的数组形状: {loaded_array_mmap.shape}”)

print(“这是一个内存映射对象,数据按需加载”)

注意:使用 mmap_mode=’r’ 时,修改 loaded_array_mmap 不会写入文件。

如果需要写入,可以使用 ‘r+’ 或 ‘w+’/’c’ (需谨慎)

一旦完成 mmap 操作,通常不需要显式关闭文件,但如果使用 ‘w+’/’c’ 可能需要flush/close。

“`

深度解析 numpy.savez

numpy.savez 函数用于将多个NumPy 数组保存到一个.npz 文件中。这个 .npz 文件是一个未压缩的 ZIP 存档。

基本语法:

python
numpy.savez(file, *args, **kwds, allow_pickle=True, fix_imports=True)

  • file: 文件名(字符串)或文件对象。如果提供文件名,会自动添加 .npz 扩展名(如果文件名没有指定扩展名的话)。
  • *args: 要保存的数组,作为位置参数传递。这些数组将被保存到 .npz 文件中,并自动命名为 arr_0, arr_1, arr_2, …
  • **kwds: 要保存的数组,作为关键字参数传递。数组的关键字名称将作为它们在 .npz 文件中的名称。使用关键字参数是推荐的方式,因为它为每个保存的数组提供了清晰、有意义的名称。
  • allow_pickle: (布尔值,默认为 True)与 save 相同,控制是否允许保存非 NumPy 对象数组。
  • fix_imports: (布尔值,默认为 True)与 save 相同,与 pickle 协议相关。

工作原理:

numpy.savez 会创建一个 ZIP 存档文件(.npz 文件)。对于每个作为参数传递的数组,它会将其序列化为一个单独的 .npy 文件,并将这个 .npy 文件添加到 ZIP 存档中。如果使用关键字参数(例如 my_array=arr),那么在 ZIP 存档中对应的 .npy 文件将命名为 my_array.npy。如果使用位置参数(例如 arr1, arr2),它们将分别被保存为 arr_0.npy, arr_1.npy 等。

优点:

  1. 保存多个数组: 核心优势,可以将多个相关的数组存储在一个文件中,便于组织和管理。
  2. 使用关键字参数命名: 允许为保存的数组指定有意义的名称,提高代码可读性,并方便加载时按名称访问。
  3. 方便分发: 多个相关数据集可以打包成一个文件进行分享。

缺点:

  1. 文件大小可能较大: savez 创建的是未压缩的 ZIP 存档。虽然避免了压缩/解压缩的计算开销,但最终文件大小可能会比单独保存每个数组再手动压缩更大(因为 ZIP 格式本身的开销)。
  2. 加载方式不同: 使用 numpy.load 加载 .npz 文件时,返回的是一个 NpzFile 对象(一个类似字典的对象),需要通过键(即保存时使用的名称)来访问具体的数组。这比加载单个 .npy 文件多一步操作。
  3. 不支持内存映射(对于 NpzFile 对象整体): numpy.load 加载 .npz 文件得到的 NpzFile 对象本身不支持内存映射。虽然 NpzFile 对象内部的每个数组(在被访问时)可以看作是从 ZIP 存档中提取并加载到内存的 .npy 数据,但无法像加载单个 .npy 文件那样直接对整个 .npz 文件进行内存映射级别的操作。加载特定键对应的数组时,该数组会被完全加载到内存。

示例代码:

“`python
import numpy as np

创建多个 NumPy 数组

array_a = np.array([1, 2, 3, 4, 5])
array_b = np.arange(10).reshape(2, 5)
array_c = np.random.rand(3, 3)

print(f”要保存的数组 A 形状: {array_a.shape}”)
print(f”要保存的数组 B 形状: {array_b.shape}”)
print(f”要保存的数组 C 形状: {array_c.shape}”)

使用 savez 保存多个数组到文件 (使用关键字参数)

file_name_savez_kw = ‘multiple_arrays_kw.npz’
np.savez(file_name_savez_kw, arr_a=array_a, matrix_b=array_b, random_c=array_c)
print(f”多个数组已保存到文件: {file_name_savez_kw} (使用关键字)”)

使用 savez 保存多个数组到文件 (使用位置参数)

file_name_savez_pos = ‘multiple_arrays_pos.npz’
np.savez(file_name_savez_pos, array_a, array_b, array_c)
print(f”多个数组已保存到文件: {file_name_savez_pos} (使用位置参数)”)

加载使用关键字参数保存的 npz 文件

loaded_data_kw = np.load(file_name_savez_kw)

查看 npz 文件中包含的数组名称 (键)

print(f”文件 {file_name_savez_kw} 中的键: {list(loaded_data_kw.keys())}”)

通过键访问具体的数组

loaded_array_a_kw = loaded_data_kw[‘arr_a’]
loaded_array_b_kw = loaded_data_kw[‘matrix_b’]
loaded_array_c_kw = loaded_data_kw[‘random_c’]

print(f”从 {file_name_savez_kw} 加载 arr_a 的形状: {loaded_array_a_kw.shape}”)
print(f”从 {file_name_savez_kw} 加载 matrix_b 的形状: {loaded_array_b_kw.shape}”)
print(f”从 {file_name_savez_kw} 加载 random_c 的形状: {loaded_array_c_kw.shape}”)

print(f”数组 A 是否一致: {np.array_equal(array_a, loaded_array_a_kw)}”)
print(f”数组 B 是否一致: {np.array_equal(array_b, loaded_array_b_kw)}”)
print(f”数组 C 是否一致: {np.array_equal(array_c, loaded_array_c_kw)}”)

重要:加载 .npz 文件后,返回的是一个类似文件句柄的对象,使用完毕后应关闭以释放资源

loaded_data_kw.close()
print(f”已关闭文件 {file_name_savez_kw}”)

加载使用位置参数保存的 npz 文件

loaded_data_pos = np.load(file_name_savez_pos)

查看 npz 文件中包含的数组名称 (键) – 自动命名

print(f”文件 {file_name_savez_pos} 中的键 (自动命名): {list(loaded_data_pos.keys())}”)

通过自动生成的键访问数组

loaded_array_a_pos = loaded_data_pos[‘arr_0’]
loaded_array_b_pos = loaded_data_pos[‘arr_1’]
loaded_array_c_pos = loaded_data_pos[‘arr_2’]

print(f”从 {file_name_savez_pos} 加载 arr_0 的形状: {loaded_array_a_pos.shape}”)
print(f”数组 A 是否一致 (从位置参数加载): {np.array_equal(array_a, loaded_array_a_pos)}”)

关闭文件句柄

loaded_data_pos.close()
print(f”已关闭文件 {file_name_savez_pos}”)
“`

介绍 numpy.savez_compressed

numpy.savez_compressednumpy.savez 的一个变种,它也用于将多个数组保存到一个 .npz 文件中,但主要的区别在于它使用了 zlib 压缩来减小文件大小。

基本语法:

python
numpy.savez_compressed(file, *args, **kwds, allow_pickle=True, fix_imports=True)

语法与 savez 完全相同。

工作原理:

savez 类似,它创建一个 ZIP 存档,并将每个数组序列化为内部的 .npy 文件。但是,在将这些 .npy 文件添加到 ZIP 存档时,它会应用 zlib 压缩。

优点:

  1. 保存多个数组: 继承了 savez 的多数组保存能力。
  2. 显著减小文件大小: 对于包含许多重复值、规则模式或浮点数组(尤其是精度要求不是极高时)的数据,压缩可以极大地减少磁盘空间占用。这对于存储和传输大量数据非常有利。
  3. 使用关键字参数命名: 同样支持为数组指定有意义的名称。

缺点:

  1. 保存和加载速度较慢: 压缩和解压缩过程需要额外的计算,因此与 savesavez 相比,savez_compressed 在保存和加载时会花费更多的时间。这是一个典型的空间换时间的权衡。
  2. 加载方式相同: 加载时同样返回 NpzFile 对象,需要按键访问,且不支持对整个 .npz 文件的内存映射。

示例代码:

“`python
import numpy as np
import os
import time

创建一些数组,包含一些容易压缩的数据(例如,有很多零或重复值)

array_large_zeros = np.zeros((1000, 1000))
array_large_rand = np.random.rand(1000, 1000)
array_mixed = np.hstack((array_large_zeros, array_large_rand))

print(f”大型零数组形状: {array_large_zeros.shape}”)
print(f”大型随机数组形状: {array_large_rand.shape}”)
print(f”混合数组形状: {array_mixed.shape}”)

file_name_savez = ‘large_arrays_uncompressed.npz’
file_name_savez_compressed = ‘large_arrays_compressed.npz’

使用 savez 保存 (未压缩)

start_time = time.time()
np.savez(file_name_savez, zeros=array_large_zeros, rand=array_large_rand, mixed=array_mixed)
end_time = time.time()
size_uncompressed = os.path.getsize(file_name_savez)
print(f”使用 savez 保存耗时: {end_time – start_time:.4f} 秒”)
print(f”savez 文件大小: {size_uncompressed / (1024*1024):.2f} MB”)

使用 savez_compressed 保存 (压缩)

start_time = time.time()
np.savez_compressed(file_name_savez_compressed, zeros=array_large_zeros, rand=array_large_rand, mixed=array_mixed)
end_time = time.time()
size_compressed = os.path.getsize(file_name_savez_compressed)
print(f”使用 savez_compressed 保存耗时: {end_time – start_time:.4f} 秒”)
print(f”savez_compressed 文件大小: {size_compressed / (1024*1024):.2f} MB”)
print(f”压缩节省空间比例: {(1 – size_compressed / size_uncompressed):.2%}”)

模拟加载速度差异

加载未压缩

start_time = time.time()
loaded_data_uncompressed = np.load(file_name_savez)
loaded_zeros_uncompressed = loaded_data_uncompressed[‘zeros’]
loaded_rand_uncompressed = loaded_data_uncompressed[‘rand’]
loaded_mixed_uncompressed = loaded_data_uncompressed[‘mixed’]
end_time = time.time()
print(f”使用 savez 加载耗时: {end_time – start_time:.4f} 秒”)
loaded_data_uncompressed.close()

加载压缩

start_time = time.time()
loaded_data_compressed = np.load(file_name_savez_compressed)
loaded_zeros_compressed = loaded_data_compressed[‘zeros’]
loaded_rand_compressed = loaded_data_compressed[‘rand’]
loaded_mixed_compressed = loaded_data_compressed[‘mixed’]
end_time = time.time()
print(f”使用 savez_compressed 加载耗时: {end_time – start_time:.4f} 秒”)
loaded_data_compressed.close()

清理文件

os.remove(file_name_savez)

os.remove(file_name_savez_compressed)

``
通过上面的示例,您会看到
savez_compressed在文件大小上的巨大优势,尤其对于包含大量重复数据(如array_large_zeros`)的场景。但同时,其保存和加载时间会相对更长。

NumPy Load:加载保存的数据

所有使用 numpy.save, numpy.savez, numpy.savez_compressed 保存的文件,都统一使用 numpy.load 函数来加载。numpy.load 会根据文件的格式自动识别是 .npy 还是 .npz,并采取相应的加载机制。

基本语法:

python
numpy.load(file, mmap_mode=None, allow_pickle=False, fix_imports=True, encoding='ASCII')

  • file: 文件名(字符串)或文件对象。
  • mmap_mode: (字符串,可选)仅对 .npy 文件有效。如果设置为 'r', 'r+', 'w+', 'c',会启用内存映射。
  • allow_pickle: (布尔值,默认为 False)是否允许加载使用 pickle 协议保存的对象。出于安全原因,默认为 False 是推荐设置。如果加载一个包含 pickle 数据的 .npy.npz 文件时此参数为 False,将会引发 ValueError
  • fix_imports: (布尔值,默认为 True)与 pickle 相关。
  • encoding: (字符串,默认为 ‘ASCII’)加载 Python 2 pickle 文件时使用的编码。

加载 .npy 文件: np.load('my_array.npy') 会直接返回保存的那个 NumPy 数组。

加载 .npz 文件: np.load('my_arrays.npz') 会返回一个 NpzFile 对象。这是一个类似字典的对象,可以通过方括号 [] 和键名来访问其中保存的各个数组。例如 data['array_name']重要提示: NpzFile 对象在访问具体数组时才会真正从文件中读取数据。一旦您完成了对数据的读取,应该调用 NpzFile 对象的 .close() 方法来释放文件资源,尤其是在循环中处理大量 .npz 文件时,不关闭文件句柄可能导致资源耗尽或文件锁问题。在 with 语句中使用 np.load 可以确保自动关闭,这是一个推荐的模式。

示例:加载并关闭 .npz 文件

“`python
import numpy as np

假设已经有了 multiple_arrays_kw.npz 文件

推荐使用 with 语句加载 .npz 文件

try:
with np.load(‘multiple_arrays_kw.npz’) as loaded_data:
print(f”文件中的键: {list(loaded_data.keys())}”)
# 访问数组
array_a = loaded_data[‘arr_a’]
array_b = loaded_data[‘matrix_b’]
print(f”加载的 array_a: {array_a}”)
print(f”加载的 array_b 形状: {array_b.shape}”)
# with 块结束,文件已自动关闭
print(“文件已自动关闭 (使用 with 语句)”)

# 如果不使用 with 语句,需要手动关闭
loaded_data_manual = np.load('multiple_arrays_kw.npz')
print(f"手动加载文件中的键: {list(loaded_data_manual.keys())}")
# 访问数组
# ... 使用 loaded_data_manual ...
loaded_data_manual.close()
print("文件已手动关闭 (.close())")

except FileNotFoundError:
print(“文件未找到,请先运行保存示例”)
“`

NumPy save vs savez:如何选择?综合对比与决策指南

经过上面的详细分析,我们可以总结 numpy.savenumpy.savez (包括 savez_compressed) 之间的核心差异和适用场景,并构建一个决策流程。

核心差异概览:

特性 numpy.save numpy.savez numpy.savez_compressed
保存对象数量 单个 NumPy 数组 多个 NumPy 数组 多个 NumPy 数组
文件格式 .npy .npz (未压缩 ZIP 存档) .npz (zlib 压缩 ZIP 存档)
文件结构 文件头 + 单个数组二进制数据 ZIP 存档,包含多个 .npy 文件 压缩 ZIP 存档,包含多个 .npy 文件
命名方式 无需命名(文件名即标识) 位置参数 (arr_0, arr_1…) 或 关键字参数 (自定义名称) 位置参数 (arr_0, arr_1…) 或 关键字参数 (自定义名称)
文件大小 适用于单数组,紧凑 通常大于等效的单个 .npy 文件总和,未压缩 通常小于等效的单个 .npy 文件总和,显著节省空间(取决于数据可压缩性)
保存/加载速度 最快(直接二进制 I/O) 较快(打包/解包有开销) 较慢(包含压缩/解压缩计算)
内存映射 支持 (numpy.loadmmap_mode) 不支持(对整个 .npz 文件) 不支持(对整个 .npz 文件)
加载返回类型 NumPy 数组 NpzFile 对象 (类似字典) NpzFile 对象 (类似字典)
文件管理 一个数组一个文件,可能导致文件碎片化 多个数组一个文件,便于组织 多个数组一个文件,便于组织

决策指南:

选择使用 savesavez 还是 savez_compressed,主要取决于以下几个因素:

  1. 您需要保存多少个数组?

    • 只有一个数组: 优先考虑 numpy.save。它简单、快速、高效,且支持内存映射,特别适合保存大型单数组。
    • 有多个数组,且这些数组是相关的,希望打包在一起: 排除 numpy.save,考虑 numpy.saveznumpy.savez_compressed
  2. 对文件大小是否有严格要求?或者数据是否具有良好的可压缩性?

    • 文件大小不敏感,或者数据不易压缩(例如,纯随机浮点数): 可以使用 numpy.savez。它比 savez_compressed 保存和加载更快,同时提供了多数组打包的功能。
    • 文件大小是重要考虑因素,需要最大限度地节省磁盘空间或降低传输带宽;或者数据包含大量重复值、零、规则模式等易压缩的特性: 选择 numpy.savez_compressed。牺牲一定的速度换取显著的文件大小减少。
  3. 对保存和加载速度有什么要求?

    • 速度是首要考虑,尤其是需要频繁读写或处理实时数据: 如果是单个数组,用 save。如果是多个数组,如果文件大小不是大问题,用 savezsavez_compressed 通常是最慢的。
  4. 是否需要内存映射功能?

    • 需要对一个非常大的数组进行内存映射,以避免一次性加载到内存: 只能使用 numpy.save 将其保存为 .npy 文件,然后用 numpy.load(..., mmap_mode=...) 加载。savezsavez_compressed 不支持对整个 .npz 文件进行内存映射。
  5. 文件管理的便利性?

    • 希望将一组相关的数组作为一个整体进行管理、存储或传输: 使用 savezsavez_compressed。一个 .npz 文件比多个 .npy 文件更容易管理。

总结的决策树:

您有多少个NumPy数组需要保存?
├── 单个数组
│ └──> 使用 numpy.save
│ (优点: 简单, 快速, .npy文件, 支持mmap)

└── 多个数组
└──> 这些数组是否需要打包到一个文件? (通常是的)
└──> 是否需要极致地减小文件大小? (数据易压缩,或存储/传输受限)
├── 是
│ └──> 使用 numpy.savez_compressed
│ (优点: 保存多个数组到一个.npz, 显著减小文件大小)
│ (缺点: 保存/加载速度较慢)

└── 否 (文件大小不太敏感,或速度更优先)
└──> 使用 numpy.savez
(优点: 保存多个数组到一个.npz, 保存/加载速度比compressed快)
(缺点: 文件大小通常比compressed大)

实际应用场景举例:

  • 保存大型单张图像数据(例如科学成像、医学影像): numpy.save.npy 文件,便于后续使用 mmap_mode 高效加载进行处理。
  • 保存机器学习模型的参数(权重、偏置),可能有多个层或组件的参数数组: numpy.saveznumpy.savez_compressed。使用关键字参数为每个参数组命名(如 weights_layer1, bias_layer1, weights_layer2),方便加载时识别和应用。如果模型参数量巨大且磁盘空间紧张,可以考虑 savez_compressed
  • 保存科学模拟的多步结果: 每次模拟可能生成几个相关的数组(如当前状态、速度、温度等)。使用 numpy.savezsavez_compressed 将同一时间步的所有结果保存在一个 .npz 文件中,文件名可以包含时间信息,方便按时间步加载和分析。
  • 保存实验数据,包含原始测量数据、处理后的特征、对应的标签等: 同样适合使用 savezsavez_compressed 将这些相关的数组打包在一起,例如 np.savez('experiment_001.npz', raw=raw_data, features=features, labels=labels)

allow_pickle=False 的重要性

save, savez, savez_compressed 以及 load 函数中都有 allow_pickle 参数,默认都是 True(在较旧的 NumPy 版本中)。但从安全角度考虑,强烈建议在不需要保存/加载非 NumPy 对象的数组时,将 load 函数的 allow_pickle 参数明确设置为 False

NumPy 的 .npy.npz 格式本身是二进制的,不涉及任意代码执行。然而,NumPy 允许在数组的 dtypeobject 时,使用 Python 的 pickle 协议来序列化数组中的每个元素(这些元素可以是任意 Python 对象)。pickle 协议是强大的,但也存在安全风险:加载一个特制的 pickle 文件可能导致执行恶意代码。

如果您的数组元素不是 Python 对象(即数组的 dtype 不是 object),而是标准的数值类型(如 int, float, bool 等),那么无论 allow_pickleTrue 还是 False,保存和加载的行为都是一样的,不会使用 pickle。

但是,如果您不确定文件的来源,或者文件可能由包含 object dtype 数组的代码生成,将 np.loadallow_pickle 设置为 False 可以有效防止加载可能包含恶意 pickle 数据的对象数组。

从 NumPy 1.16.3 和 1.17.0 版本开始,np.loadallow_pickle 默认值被改为 False,这是一个重要的安全改进。但在使用旧版本或为了代码向前兼容性,显式设置它是一个好习惯。保存时,如果数组 dtypeobject 并且 allow_pickle=False,则会抛出错误。

总结与结语

NumPy 的 savesavez (以及 savez_compressed) 是进行 NumPy 数组持久化的核心工具。它们各有优势,针对不同的数据存储需求提供了高效的解决方案。

  • numpy.save 单个数组的首选,简单、快速、支持内存映射,适用于保存大型独立数据集。
  • numpy.savez 多个相关数组的容器,便于组织和管理,保存加载速度较快,适用于对文件大小不太敏感的场景。
  • numpy.savez_compressed 多个相关数组的压缩容器,牺牲部分速度换取文件大小的显著减小,适用于存储空间或带宽有限、且数据可压缩性好的场景。

在实际应用中,根据您要保存的数组数量、对文件大小和速度的要求以及是否需要内存映射等因素,参考本文提供的对比分析和决策指南,选择最合适的函数。同时,注意在使用 numpy.load 加载 .npz 文件后关闭 NpzFile 对象,并在非必要时将 allow_pickle 设置为 False 以提高安全性。

掌握了 savesavez 的使用技巧,您将能更有效地管理和利用您的 NumPy 数据,为后续的数据分析、模型训练和结果复现打下坚实的基础。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部