18

在通过重新编译脚本更新类定义后,pickle 拒绝序列化该类先前实例化的对象,并给出错误:“Can't pickle object: it's not the same object as”

有没有办法告诉泡菜它应该忽略这种情况?要仅按名称识别类,请忽略导致不匹配的内部唯一 ID?

我肯定会欢迎作为答案的替代等效模块的建议,它以方便和强大的方式解决了这个问题。


作为参考,这是我的动机:

我正在创建一个高生产力、快速迭代的开发环境,在其中实时编辑 Python 脚本。脚本被反复重新编译,但数据在编译过程中仍然存在。作为生产力目标的一部分,我尝试使用 pickle 进行序列化,以避免为不断变化的数据结构编写和更新显式序列化代码的成本。

大多数情况下,我序列化内置类型。我小心地避免在我腌制的类中进行有意义的更改,并且在必要时我使用 copy_reg.pickle 机制对 unpickle 执行上转换。

脚本重新编译完全阻止我腌制对象,即使类定义实际上没有改变(或只是以良性方式改变)。

4

3 回答 3

12

除非您可以解压缩早期版本的类定义,否则需要转储和加载实例的引用 pickle 现在已经不存在了。所以这是“不可能的”。

但是,如果你确实想这样做,你可以保存你的类定义的以前版本......然后你就不得不欺骗pickle来引用你的旧/保存的类定义,而不是使用最新的 - 这可能只是编辑obj.__class__obj.__module__指向您的旧课程。您的类实例中可能还存在一些其他奇怪的东西,它们也引用了您必须处理的旧类定义。此外,如果您添加或删除一个类方法,您可能会遇到一些意想不到的结果,或者必须相应地更新实例。另一个有趣的转折是你可以让 unpickler 总是使用你类的最新版本。

我的序列化包dill有一些方法可以将编译的源代码从实时代码对象转储到临时文件,然后使用该临时文件进行序列化。它是包装中较新的部分之一,因此不如莳萝的其他部分坚固。此外,您的用例不是我考虑过的用例,但我可以看到它是一个很好的功能。

于 2013-10-14T14:42:39.240 回答
4

有一种简单的方法可以做到这一点,基本上就是用户的回答

首先,我将给出失败的代码:

#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 忽略该类的两个版本之间的任何差异时,此解决方案才有效。如果两个版本有显着差异,我不希望这个解决方案有效。

于 2019-06-09T00:37:38.620 回答
2

Two solutions come into my mind:

  1. before you pickle you can set object.__class__

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

    Maybe you can use persistent_id for this because every object is passed to it.

  2. define __reduce__ to do the exact same as pickle does. (have a look at pickle.py for this)

于 2013-04-29T14:53:26.787 回答