代码之家  ›  专栏  ›  技术社区  ›  Mathieu

读取pickle文件时出错

  •  3
  • Mathieu  · 技术社区  · 7 年前

    在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)...]
    

    当然,信号是一个物体 信号 . 此列表将以.pkl格式保存。此外,我正在为 计算数据 功能。许多迭代将使用过去计算的数据作为起点,从而读取相应的和需要的.pkl文件。

    最后,我同时使用多台计算机,每台计算机都将计算的数据保存在本地网络上。因此,每台计算机都可以访问其他计算机生成的数据,并将其用作起点。

    返回错误:

    我的主要问题是,当我通过双击文件或Windows命令或PowerShell启动程序时,我从未出现过此错误。程序从不崩溃并抛出此错误,运行时没有明显的问题。

    然而, 我无法读取Spyder中的.pkl文件。每次我尝试,都会抛出错误。

    知道我为什么会有这种奇怪的行为吗?

    谢谢!

    1 回复  |  直到 7 年前
        1
  •  6
  •   Dunes    7 年前

    当你把东西扔到 pickle 您应该避免主模块中声明的酸洗类和函数。您的问题(部分)是因为您的程序中只有一个文件。 泡菜 是惰性的,不序列化类定义或函数定义。相反,它保存了一个如何查找类(它所在的模块及其名称)的引用。

    当python直接运行脚本/文件时,它将程序作为 __main__ 模块(不考虑其实际文件名)。但是,当文件被加载并且 主模块 import program )然后,它的模块名基于它的名称。所以 program.py 接到电话 program .

    当您从命令行运行时,您正在执行前一个命令,并调用模块 _主__ . 因此,pickle创建对类的引用,例如 __main__.Signal . 什么时候? spyder 尝试加载命令它导入的pickle文件 _主__ 寻找 Signal . 但是,斯派德的 _主__ 模块是用来启动的模块 斯皮德 而不是你的 程序.py 所以泡菜找不到 信号 .

    您可以通过运行来检查pickle文件的内容( -a 打印每个命令的说明)。从中您将看到您的类被引用为 _主信号 .

    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
    

    解决

    您可以使用多种解决方案:

    1. 不要序列化在您的 _主__ 模块。最简单最好的解决方案。相反,将这些类移动到另一个模块,或者编写一个 main.py 用于调用程序的脚本(这两个脚本都意味着在 _主__ 模块)。
    2. 编写自定义Derserialiser
    3. 编写自定义序列化程序

    以下解决方案将使用名为pickle的pickle文件 out.pkl 由以下代码创建(在名为 程序.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)
    

    自定义反序列化程序解决方案

    您可以编写一个客户反序列化程序,它知道何时遇到对 _主__ 模块你真正的意思是 程序 模块。

    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文件。

    自定义序列化解决方案

    与前面的解决方案相比,您可以确保任何人都可以轻松地反序列化序列化的pickle对象,而无需了解自定义反序列化逻辑。为此,您可以使用 copyreg 通知模块 泡菜 如何反序列化各种类。所以在这里,你要做的就是告诉我 泡菜 反序列化的所有实例 _主__ 类,就好像它们是 程序 课程。您需要为每个类注册一个自定义序列化程序。

    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)