稳定的Python序列化(例如,无pickle模块重定位问题)

0 投票
1 回答
723 浏览
提问于 2025-04-15 13:51

我正在考虑使用Quantities这个库来定义一个数字和它的单位。这个值很可能需要存储到磁盘上。你可能知道,使用“序列化”(也叫pickling)有一个大问题:如果你移动了模块的位置,反序列化(unpickling)就无法找到类,这样你就无法恢复之前存储的信息。虽然有一些解决这个问题的方法,但这些方法其实都是变通的。

我想出的一个解决方案是创建一个字符串,这个字符串能唯一表示一个单位。当你从磁盘上获取这个字符串后,可以把它传给Quantities模块中的一个工厂方法,这个方法会把字符串解码成一个合适的单位实例。这样做的好处是,即使你移动了模块,只要把这个特殊的字符串传给工厂方法,所有东西依然可以正常工作。

这个概念是大家都知道的吗?

1 个回答

2

这段话提到的是一个叫做“惠勒第一原则”的概念,意思是“计算机科学中的所有问题都可以通过增加一个间接层来解决”。而第二个原则则补充说,这样做通常会产生另一个问题。简单来说,你需要通过增加一个间接层来识别某种类型。对于类似于“序列化”的方法,实体和类型之间的关系是可以处理的(你可以研究一下pickle.pycopy_reg.py的源代码,了解更多细节)。

具体来说,我认为你需要做的是创建一个pickle.Pickler的子类,并重写save_inst方法。在当前版本中,它的内容是:

    if self.bin:
        save(cls)
        for arg in args:
            save(arg)
        write(OBJ)
    else:
        for arg in args:
            save(arg)
        write(INST + cls.__module__ + '\n' + cls.__name__ + '\n')

你需要写一些不同于仅仅是类的模块和名称的东西——也就是说,为这个类创建一个独特的标识符(由两个字符串组成),这个标识符可能存储在你自己的注册表中;对于save_global方法也是一样。

对于你的Unpickler子类来说,这个过程更简单,因为_instantiate部分已经被提取到一个单独的方法中:你只需要重写find_class方法,它的内容是:

def find_class(self, module, name):
    # Subclasses may override this
    __import__(module)
    mod = sys.modules[module]
    klass = getattr(mod, name)
    return klass

这个方法必须接受两个字符串并返回一个类对象;你可以通过你的注册表来实现这一点。

每当涉及到注册表时,你需要考虑如何确保注册所有感兴趣的对象(类)等等。这里有一个常见的策略是,不去动序列化的过程,而是确保所有类的移动、模块的重命名等都被记录在某个永久的地方;这样,只有子类化的反序列化器可以完成所有工作,并且它可以在重写的find_class中方便地完成这一切——避免了所有注册的问题。我知道你把这看作是一个“变通方法”,但在我看来,这只是“增加一个间接层”概念的一个极其简单、强大和方便的实现,避免了“产生另一个问题”的困扰;-)

撰写回答