【问题标题】:Pickle: dealing with updated class definitionsPickle:处理更新的类定义
【发布时间】:2013-04-22 13:17:50
【问题描述】:

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

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

我绝对欢迎作为答案的替代等效模块的建议,它以方便和稳健的方式解决了这个问题。


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

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

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

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

【问题讨论】:

标签: python pickle recompile hotswap


【解决方案1】:

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

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

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

【讨论】:

  • 好的,我已经在github上的最新版本中添加了这个功能。实现的技巧比我想象的要少得多......只需用泡菜序列化类定义,瞧。
  • 奇怪我想做相反的事情。我使用 dill 来保存一个对象,但是当我调用新方法时,它不会返回我期望的字符串。所以它可能使用旧定义?使用 dill 加载旧对象后,如何强制加载旧定义?
  • @CharlieParker:没有一个例子很难说出你在问什么。也许你可以提交一个关于 SO 的新问题,或者在 Github 上填写一个问题?
【解决方案2】:

有一个简单的方法,基本上是User's answer。

首先我会给出失败的代码:

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

【讨论】:

    【解决方案3】:

    我想到了两个解决方案:

    1. 在你腌制之前你可以设置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 完全相同。 (看看pickle.py这个)

    【讨论】:

    • 我成功使用第一种方法为我重命名的类转换了pickle文件。
    猜你喜欢
    • 2022-10-04
    • 2017-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-15
    • 1970-01-01
    相关资源
    最近更新 更多