Numpy 加载包含pickled数据的文件时出错

Numpy 加载包含pickled数据的文件时出错

在Python中,pickling是将Python对象序列化为字节流的过程。如果您需要将一个对象存储在磁盘上,或者在网络上传输它,那么你必须将它pickling为一个字符串。在另一个程序中,你可以读取这个字符串并恢复原始对象。Numpy是一个基于Python的科学计算库,它经常处理包含pickle格式数据的文件,但是Numpy在从.npy文件中加载pickled数据时会遇到一些问题。

阅读更多:Numpy 教程

Numpy and Pickling

Numpy在处理数据时使用pickle。pickle是Python标准库中的一个模块,用于将Python对象转换为序列化的字节流,并在需要时将其反序列化为Python对象。使用pickle,您可以保存Python中的任何对象,包括自定义类,函数和方法,并在需要时重构为Python对象。Numpy继承了pickle,使用pickle将多维数组,数据类型和其他Numpy对象序列化为字节流。Numpy还通过将pickle格式和Numpy的二进制文件格式.npz和.npy混合使用,实现快速读写多维数据。虽然Numpy提供了一种有效的方法来处理pickle数据,但是从.npz和.npy文件中加载pickled数据时会出现一些奇怪的问题。

Numpy无法加载包含pickled数据的文件

当您尝试从.npz或.npy文件中加载包含pickled数据的文件时,您可能会遇到以下错误消息:

ValueError: Object arrays cannot be loaded when allow_pickle=False

这意味着什么?这有点不直观。您可能已经设置了np.load函数的allow_pickle参数为True,但是仍然遇到此错误。实际上,这个错误消息是非常有用的,因为它说明了您的错误。allow_pickle参数控制在加载二进制文件时是否允许pickle数据。设置这个参数为False会使Numpy直接加载二进制数据,而不进行任何pickle操作。这个错误消息说明您正在尝试加载一个不允许pickle的对象数组。这很疑惑,因为我们没有显式地要求Numpy将任何对象数组保存为pickle对象。

问题分析

我们要解释这个奇怪的问题,需要了解Numpy二进制文件的存储格式。当您创建一个包含Numpy对象的数组之后,您可能会将其保存到磁盘上,这样您就可以在以后的时间重新加载它。您可以使用.npz.npy扩展名将Numpy数组保存到磁盘上。

.npz文件格式

Numpy压缩格式.npz是将一个或多个包含Numpy数组的npy格式文件压缩成一个单独的ZIP文件,保留每个数组的名称。npz文件包含至少一个.npy文件,并存储该文件的名称和数据。您可以使用np.savez方法将Numpy数组保存到.npz文件中以便以后加载。

.npy文件格式

.npy文件以二进制形式存储Numpy数组数据。这些文件包括一个-64字节的魔数字符串,后跟元数据头和数组数据。对于多维数组,元数据头包含维度的数量,每个维度的大小以及该数组的数据类型。例如,下面的表格显示了一个2D数组的.npy文件元数据头。

Byte offset C Type 解释
0 char[6] 魔数(“.npy”)
6 uint8 主要版本号:NPY_MAJOR_VERSION
7 uint8 次要版本号:NPY_MINOR_VERSION| 8 | uint16 | 该数组的头部长度,以字节为单位
10 uint8 该数组的数据类型
11 uint32 该数组的维度数
15 uint64 第一个维度的大小
23 uint64 第二个维度的大小

值得注意的是,这里的第11个字节表示的是维度数,也就是描述该数组大小的整数列表(即,shape)。元数据头后的字节就是该数组的数据,顺序按照C语言的内存布局顺序存储。

回到我们的问题,那么为什么Numpy会在.npy文件中使用pickle数据呢?关键在于,如果一个Numpy数组不是C语言中的原始数据类型(如整数、浮点数、字符和布尔值),那么它需要作为Python对象来序列化。这会在Numpy压缩格式.npz中出现,因为它可以存储一个Python字典,其中每个值可以是一个Python对象。但在一个.npy文件中,所有的数组元素都必须是同一种C数据类型。因此,如果一个数组包含字符串、列表、元组或自定义类的元素等对象,它必须被pickling,才能被存储在.npy文件中。

解决方案

理解问题的原因是解决问题的一半。在我们的情况下,.npy文件中包含了一个不支持pickle的对象数组。为了解决这个问题,有以下两种方法:

1. 设置allow_pickle=True

在使用np.load函数加载一个包含pickle数据的.npy文件时,设置allow_pickle=True可以解决问题,因为这会告诉Numpy在加载数据时解开pickle数据。默认情况下,allow_pickle是关闭的。下面是以下代码示例:

import numpy as np

data = np.load('data.npy', allow_pickle=True)

但是,请注意,在开启allow_pickle选项时存在潜在的风险,例如加载可能包含恶意代码的pickle数据,因为pickle数据是可执行的Python代码。因此,应当只使用可信任来源的pickle数据。

2. 手动读取二进制文件

另一种方法是使用Python的内置模块pickle手动加载二进制文件并解pickle。通过这种方式,您可以选择在手动转换数据之前检查后pickle数据是否可信,以确保不会执行恶意代码。以下是一个代码示例:

import pickle

with open('data.npy', 'rb') as f:
    # 跳过numpy格式的头信息
    f.seek(128)  
    # 读取pickle数据
    data = pickle.load(f, encoding='latin1')

在这个例子中,我们使用open函数打开.npy文件,并跳过了Numpy数组的头部信息。我们使用pickle模块手动处理两个步骤:首先反序列化pickle数据,并检查其是否可信。

总结

当尝试从一个包含pickle数据的根二进制文件中加载数据时,Numpy会遇到不能读取对象数组的问题。这种常见的错误信息告诉我们,该数组不支持pickle加载。为了解决这个问题,我们要注意.npy文件中对象数组的存在并使用allow_pickle=True或手动读取二进制文件的下面pickle模块来处理pickle数据。注意,pickle数据可能包含可执行代码,因此要确保其来源是可信任的。

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程