Python pickle: работа с обновленными определениями классов

После определения класса обновляется путем перекомпиляции script, pickle отказывается сериализовать ранее созданные объекты этого класса, выдавая ошибку: "Невозможно рассортировать объект: это не тот же объект, что и"

Есть ли способ рассказать, что он должен игнорировать такие случаи? Чтобы просто идентифицировать классы по имени, игнорируйте, какой внутренний уникальный идентификатор вызывает несоответствие?

Я бы определенно приветствовал в качестве ответа предложение альтернативного эквивалентного модуля, который решает эту проблему удобным и надежным способом.


Для справки, здесь моя мотивация:

Я создаю среду с высокой производительностью, быстрой итерацией, в которой скрипты Python редактируются в реальном времени. Сценарии повторно перекомпилируются, но данные сохраняются во всех компиляторах. В рамках целей производительности я пытаюсь использовать pickle для сериализации, чтобы избежать затрат на запись и обновление явного кода сериализации для постоянно изменяющихся структур данных.

В основном я сериализую встроенные типы. Я стараюсь избегать значимых изменений в классах, которые я мариную, и при необходимости я использую механизм copy_reg.pickle для выполнения upconversion на unpickle.

Script перекомпиляция не позволяет мне травить объекты вообще, даже если определения классов фактически не изменились (или только изменились мягко).

Ответ 1

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

Однако, если вы действительно захотите это сделать, вы можете сохранить предыдущие версии определений классов... и тогда вам просто нужно было обмануть марихуану, ссылаясь на ваши старые/сохраненные определения классов и не используя самые современные, что может просто означать редактирование obj.__class__ или obj.__module__, чтобы указать на ваш старый класс. В вашем экземпляре класса могут быть и другие нечетные вещи, которые также относятся к определению старого класса, которое вам нужно будет обрабатывать. Кроме того, если вы добавляете или удаляете метод класса, вы можете столкнуться с некоторыми неожиданными результатами или иметь дело с обновлением экземпляра соответствующим образом. Еще один интересный поворот в том, что вы можете заставить unpickler всегда использовать самую последнюю версию вашего класса.

Мой пакет сериализации, dill, имеет несколько методов, которые могут скомпилировать исходный код из живого кодового объекта во временный файл, а затем сериализуйте с использованием этого временного файла. Это одна из новых частей пакета, поэтому она не такая прочная, как остальная укроп. Кроме того, ваш прецедент - это не тот случай использования, который я рассматривал, но я мог видеть, как это будет хорошая функция.

Ответ 2

Мне приходят в голову два решения:

  • прежде чем вы соберете, вы можете установить object.__class__

    >>> class X(object):
        pass
    
    >>> class Y(object):
        pass
    
    >>> x = X()
    >>> x.__class__ = Y
    >>> type(x)
    <class '__main__.Y'>
    

    Может быть, вы можете использовать persistent_id для этого, потому что каждый объект передается ему.

  • Определите __reduce__, чтобы сделать то же самое, что и pickle. (посмотрите на pickle.py для этого)

Ответ 3

Есть простой способ сделать это, в основном, ответ пользователя.

Сначала я дам ошибочный код:

#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 not the same object as __main__.Foo

Чтобы устранить эту проблему, просто сбросьте атрибут __class__ в foo прежде чем __class__ травление, как в ответе пользователя:

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 игнорировал любые различия между двумя версиями класса. Если две версии имеют существенные различия, я не ожидаю, что это решение будет работать.