Python pickle: работа с обновленными определениями классов
После того как определение класса обновлено путем перекомпиляции скрипта, pickle отказывается сериализовать ранее созданные экземпляры объектов этого класса, выдавая ошибку: "Невозможно выбрать объект: это не тот же объект, что и"
Есть ли способ сказать рассол, что он должен игнорировать такие случаи? Чтобы просто идентифицировать классы по имени, игнорируйте, какой внутренний уникальный идентификатор вызывает несоответствие?
Я бы определенно приветствовал в качестве ответа предложение альтернативного, эквивалентного модуля, который бы решал эту проблему удобным и надежным способом.
Для справки, вот моя мотивация:
Я создаю высокопроизводительную и быструю среду разработки, в которой скрипты Python редактируются вживую. Скрипты многократно перекомпилируются, но данные сохраняются в разных компиляциях. Как часть целей производительности, я пытаюсь использовать pickle для сериализации, чтобы избежать затрат на написание и обновление явного кода сериализации для постоянно меняющихся структур данных.
В основном я сериализую встроенные типы. Я стараюсь избегать значимых изменений в классах, которые я выбираю, и при необходимости я использую механизм copy_reg.pickle для выполнения преобразования вверх при unpickle.
Перекомпиляция скрипта вообще не позволяет мне выбирать объекты, даже если определения классов на самом деле не изменились (или изменились только доброкачественным образом).
2 ответа
Если вы не можете распаковать более раннюю версию определения класса, контрольный пакет должен выгрузить и загрузить экземпляр, теперь нет. Так что это "невозможно".
Однако, если вы захотите это сделать, вы можете сохранить предыдущие версии определений вашего класса... и тогда вам просто придется обманывать, ссылаясь на старые / сохраненные определения классов, а не использовать самые последние из них - что может быть просто редактирование obj.__class__
или же obj.__module__
указать на ваш старый класс. Также в вашем экземпляре класса могут быть некоторые другие странные вещи, которые также ссылаются на старое определение класса, с которым вам придется работать. Кроме того, если вы добавляете или удаляете метод класса, вы можете столкнуться с неожиданными результатами или иметь дело с соответствующим обновлением экземпляра. Другим интересным моментом является то, что вы можете заставить сборщика всегда использовать самую последнюю версию вашего класса.
В моем пакете сериализации, dill, есть несколько методов, которые могут выгружать скомпилированный исходный код из объекта живого кода во временный файл, а затем сериализовать с использованием этого временного файла. Это одна из самых новых частей упаковки, поэтому она не такая прочная, как остальная часть укропа. Кроме того, ваш вариант использования - это не тот вариант использования, который я рассматривал, но я мог видеть, как это было бы полезно иметь.
Есть простой способ сделать это, по сути, это ответ пользователя.
Сначала я приведу код ошибки:
#Tested with Python 3.6.7
import pickle
class Foo:
pass
foo = Foo()
class Foo:
def bar(self):
return 0
pickle.dumps(foo) #raises PicklingError: Can't pickle <class '__main__.Foo'>: it's not the same object as __main__.Foo
Чтобы решить эту проблему, просто сбросьте __class__
атрибут foo
перед маринованием, как в ответе пользователя:
import pickle
class Foo:
pass
foo = Foo()
class Foo:
def bar(self):
return 0
foo.__class__ = eval(foo.__class__.__name__) #reset __class__ attribute
pickle.dumps(foo) #works fine
Это решение работает только в том случае, если вы действительно хотите, чтобы pickle игнорировал любые различия между двумя версиями класса. Если две версии имеют существенные различия, я не ожидаю, что это решение сработает.
Два решения приходят мне на ум:
перед засолкой вы можете установить
object.__class__
>>> class X(object): pass >>> class Y(object): pass >>> x = X() >>> x.__class__ = Y >>> type(x) <class '__main__.Y'>
Может быть, вы можете использовать
persistent_id
для этого, потому что каждый объект передается ему.определять
__reduce__
делать точно так же, как делает рассол. (посмотрите на pickle.py для этого)