python - 读取泡菜文件时出现AttributeError
问题描述
在 spyder (python 3.6.5) 上读取 .pkl 文件时出现以下错误:
IN: with open(file, "rb") as f:
data = pickle.load(f)
Traceback (most recent call last):
File "<ipython-input-5-d9796b902b88>", line 2, in <module>
data = pickle.load(f)
AttributeError: Can't get attribute 'Signal' on <module '__main__' from 'C:\\Python36\\lib\\site-packages\\spyder\\utils\\ipython\\start_kernel.py'>
上下文:
我的程序由一个文件组成:program.py
在程序中,Signal
定义了一个类以及许多函数。下面提供了该程序的简化概述:
import numpy as np
import _pickle as pickle
import os
# The unique class
class Signal:
def __init__(self, fq, t0, tf):
self.fq = fq
self.t0 = t0
self.tf = tf
self.timeline = np.round(np.arange(t0, tf, 1/fq*1000), 3)
# The functions
def write_file(data, folder_path, file_name):
with open(join(folder_path, file_name), "wb") as output:
pickle.dump(data, output, -1)
def read_file(folder_path, file_name):
with open(join(folder_path, file_name), "rb") as input:
data= pickle.load(input)
return data
def compute_data(# parameters):
# do stuff
该函数compute_data
将返回以下形式的元组列表:
data = [((Signal_1_1, Signal_1_2, ...), val 1), ((Signal_2_1, Signal_2_2, ...), val 2)...]
当然, Signal_i_k 是一个 object Signal
。此列表将以 .pkl 格式保存。此外,我正在使用不同的compute_data
函数参数进行大量迭代。许多迭代将使用过去的计算数据作为起点,因此将读取相应的和需要的 .pkl 文件。
最后,我同时使用多台计算机,每台计算机都将计算数据保存在本地网络上。因此,每台计算机都可以访问其他计算机生成的数据并将其用作起点。
回到错误:
我的主要问题是,当我通过双击文件或通过 windows cmd 或 PowerShell 启动程序时,我从来没有遇到过这个错误。该程序永远不会崩溃并抛出此错误并且运行时没有明显问题。
但是,我无法在 spyder 中读取 .pkl 文件。每次我尝试时,都会抛出错误。
知道为什么我会出现这种奇怪的行为吗?
谢谢!
解决方案
当你在 a 中转储东西时,pickle
你应该避免腌制在主模块中声明的类和函数。您的问题(部分)是因为您的程序中只有一个文件。pickle
是惰性的,不序列化类定义或函数定义。相反,它保存了如何查找类的参考(它所在的模块及其名称)。
当 python 直接运行脚本/文件时,它将程序作为__main__
模块运行(无论其实际文件名如何)。然而,当一个文件被加载并且不是主模块时(例如,当你做类似的事情时import program
)那么它的模块名称是基于它的名称。所以program.py
被调用program
。
当您从命令行运行时,您正在执行前者,并且该模块被称为__main__
. 因此,pickle 会创建对您的类的引用,例如__main__.Signal
. 当spyder
尝试加载 pickle 文件时,它会被告知导入__main__
并查找Signal
. 但是,spyder 的__main__
模块是用于启动的模块,spyder
而不是你的模块,program.py
所以 pickle 无法找到Signal
.
您可以通过运行检查泡菜文件的内容(-a
打印每个命令的描述)。从这里你会看到你的类被引用为__main__.Signal
.
python -m pickletools -a file.pkl
你会看到类似的东西:
0: \x80 PROTO 3 Protocol version indicator.
2: c GLOBAL '__main__ Signal' Push a global object (module.attr) on the stack.
19: q BINPUT 0 Store the stack top into the memo. The stack is not popped.
21: ) EMPTY_TUPLE Push an empty tuple.
22: \x81 NEWOBJ Build an object instance.
23: q BINPUT 1 Store the stack top into the memo. The stack is not popped.
...
51: b BUILD Finish building an object, via __setstate__ or dict update.
52: . STOP Stop the unpickling machine.
highest protocol among opcodes = 2
解决方案
有多种解决方案可供您使用:
- 不要序列化
__main__
模块中定义的类的实例。最简单和最好的解决方案。而是将这些类移动到另一个模块,或者编写一个main.py
脚本来调用您的程序(两者都意味着这些类不再在__main__
模块中找到)。 - 编写自定义反序列化器
- 编写自定义序列化程序
以下解决方案将使用out.pkl
由以下代码创建的 pickle 文件(在名为 的文件中program.py
):
import pickle
class MyClass:
def __init__(self, name):
self.name = name
if __name__ == '__main__':
o = MyClass('test')
with open('out.pkl', 'wb') as f:
pickle.dump(o, f)
自定义解串器解决方案
您可以编写一个客户反序列化器,它知道何时遇到对__main__
模块的引用,您真正的意思是program
模块。
import pickle
class MyCustomUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module == "__main__":
module = "program"
return super().find_class(module, name)
with open('out.pkl', 'rb') as f:
unpickler = MyCustomUnpickler(f)
obj = unpickler.load()
print(obj)
print(obj.name)
这是加载已创建的泡菜文件的最简单方法。该程序将责任推到反序列化代码上,而序列化代码实际上应该负责正确创建pickle文件。
自定义序列化解决方案
与之前的解决方案相比,您可以确保任何人都可以轻松地反序列化序列化的 pickle 对象,而无需了解自定义反序列化逻辑。为此,您可以使用该copyreg
模块来告知pickle
如何反序列化各种类。所以在这里,你要做的是告诉pickle
反序列化所有类的实例,__main__
就好像它们是program
类的实例一样。您需要为每个类注册一个自定义序列化程序
import program
import pickle
import copyreg
class MyClass:
def __init__(self, name):
self.name = name
def pickle_MyClass(obj):
assert type(obj) is MyClass
return program.MyClass, (obj.name,)
copyreg.pickle(MyClass, pickle_MyClass)
if __name__ == '__main__':
o = MyClass('test')
with open('out.pkl', 'wb') as f:
pickle.dump(o, f)
推荐阅读
- javascript - 如何将环境变量包含到 Webpack 5 中?
- c++ - 如何修复链接源文件时未定义的宏?
- javascript - 如何通过 GraphQL 从 json 获取数据?
- android - Android 上的 Google Firebase 会话活动归因
- sql-server - 在 Pentaho 中记录 SQL 消息
- python - 如何使用 Sikuli 和 Python 代码连接远程 Linux 服务器
- python-3.x - python Fabric 2切换用户
- css - 是否有可能获得宽度和最大宽度之间的差异?
- python - TypeError: create_bool(): 不兼容的函数参数。支持以下参数类型: 1. (arg0: bool) -> .. 我该如何解决这个错误
- android - 添加 file_picker 包时在构建中出现异常