Когда вы выгружаете вещи в 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
Решения
ТамВам доступно несколько решений:
- Не сериализуйте экземпляры классов, определенных в вашем модуле
__main__
.Самое простое и лучшее решение.Вместо этого переместите эти классы в другой модуль или напишите сценарий main.py
, чтобы вызвать вашу программу (оба будут означать, что такие классы больше не найдены в модуле __main__
). - Написать пользовательский derserialiser
- Написать собственный сериализатор
Следующие решения будут работать с файлом рассылки с именем 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)