Pickle:处理更新的类定义



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

有没有办法告诉pickle它应该忽略这些情况?仅仅通过名称标识类,忽略导致不匹配的内部唯一ID ?

我绝对欢迎一个替代的、等价的模块作为答案,它以一种方便和健壮的方式解决了这个问题。


作为参考,以下是我的动机:

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

我主要序列化内置类型。我小心地避免在pickle的类中进行有意义的更改,必要时使用copy_reg。Pickle机制在unpickle上执行上转换。

脚本重新编译使我根本无法pickle对象,即使类定义实际上没有改变(或者只是以一种良性的方式改变)。

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

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

我的序列化包dill有一些方法可以将编译后的源代码从活动代码对象转储到临时文件,然后使用该临时文件进行序列化。它是软件包中较新的部分之一,因此它不像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

要解决这个问题,只需在酸洗前重置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忽略类的两个版本之间的任何差异时才有效。如果两个版本有明显的差异,我不希望这个解决方案工作。

我想到了两个解决方案:

  1. 在pickle之前可以设置object.__class__

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

    也许你可以使用persistent_id,因为每个对象都传递给它。

  2. 定义__reduce__做与pickle完全相同的事情。

相关内容

  • 没有找到相关文章

最新更新