AttributeError при чтении файла консервирования - PullRequest
0 голосов
/ 22 мая 2018

Я получаю следующую ошибку при чтении моих файлов .pkl на spyder (python 3.6.5):

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будучи объектом Signal.Этот список будет сохранен в формате .pkl.Более того, я выполняю много итераций с различными параметрами для функций compute_data.Многие итерации будут использовать прошлые вычисленные данные в качестве отправной точки и, таким образом, будут читать соответствующие и необходимые файлы .pkl.

Наконец, я использую несколько компьютеров одновременно, каждый из которых сохраняет вычисленные данныев локальной сети.Таким образом, каждый компьютер может получить доступ к данным, сгенерированным другими, и использовать их в качестве отправной точки.

Вернуться к ошибке:

Моя главная проблема заключается в том, что у меня никогда не былоэта ошибка, когда я запускаю свои программы, дважды щелкнув по файлу или через Windows CMD или PowerShell.Программа никогда не падает, выдает эту ошибку и запускается без явных проблем.

Однако, Я не могу прочитать файл .pkl в spyder.Каждый раз, когда я пытаюсь, выдается ошибка.

Есть идеи, почему у меня такое странное поведение?

Спасибо!

1 Ответ

0 голосов
/ 22 мая 2018

Когда вы выгружаете вещи в pickle, вам следует избегать выбора классов и функций, объявленных в главном модуле.Ваша проблема (частично), потому что у вас есть только один файл в вашей программе.pickle является ленивым и не сериализует определения классов или определения функций.Вместо этого он сохраняет ссылку о том, как найти класс (модуль, в котором он живет, и его имя).

Когда python запускает скрипт / файл напрямую, он запускает программу как модуль __main__ (независимо от его фактического имени файла).Однако, когда файл загружен и является , а не основным модулем (например, когда вы делаете что-то вроде import program), тогда имя его модуля основывается на его имени.Таким образом, program.py вызывается program.

Когда вы работаете из командной строки, вы делаете первое, а модуль называется __main__.Таким образом, pickle создает ссылки на ваши классы, такие как __main__.Signal.Когда spyder пытается загрузить файл pickle, ему приказывают импортировать __main__ и искать Signal.Но, модуль __main__ spyder - это модуль, который используется для запуска 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

Решения

ТамВам доступно несколько решений:

  1. Не сериализуйте экземпляры классов, определенных в вашем модуле __main__.Самое простое и лучшее решение.Вместо этого переместите эти классы в другой модуль или напишите сценарий main.py, чтобы вызвать вашу программу (оба будут означать, что такие классы больше не найдены в модуле __main__).
  2. Написать пользовательский derserialiser
  3. Написать собственный сериализатор

Следующие решения будут работать с файлом рассылки с именем out.pkl, созданным с помощью следующего кода (в файле с именем 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 могут быть легко десериализованы кем угодно без необходимости знать собственную логику десериализации.Для этого вы можете использовать модуль 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)
...