pickle如何处理递归对象的序列化?

时间:2018-07-29 05:44:29

标签: python serialization pickle

我浏览了关于pickle (python pickle)的文档,在与元帅的比较中发现,在对一个对象进行多次引用的情况下,pickle只会序列化该对象的一个​​实例,而所有其他引用都是指向序列化对象的主副本。

但是我想知道pickle如何在递归对象的情况下序列化对象。

1 个答案:

答案 0 :(得分:0)

如果您已经了解了它是如何用指向主服务器的指针替换重复引用的,那么递归对象实际上没有什么不同,除了一个小的改动。


让我们仅用一些简单的示例,通过使用pickle实际功能的简化版本。

要转储[1, 1],您需要:

  • 进入列表
    • 转储1
    • 转储第二个1
    • 转储list-build(2)指令

如果保留所见元素的备忘录字典,则可以:

  • 在备忘录中查找id([1, 1])(看不见),因此进入列表
    • 在备忘录中查找id(1)(看不见),因此转储1,并将其添加到memo
    • 在备忘录中查找id(1)-已经存在,因此只需转储引用即可
    • 转储list-build(2)指令
    • 将列表添加到备忘录

现在,如果我们有类似lst = []; lst.append(lst)的东西并尝试转储该怎么办?

  • 在备忘录中查找id([...])(看不见),因此进入列表
    • 在备忘录中查找id([...])-未显示,因此进入列表

…哎呀,这将击中RecursionError

因此,您要做的是,在实际发出构建列表的代码之前,将id([...])的目标添加到备忘中。没关系;参考文献只需要知道位置,而不是那里的位置。所以:

  • 在备忘录中查找id([...])-未显示,因此将其添加到备忘录中,然后进入列表
    • 在备忘中查找id([...])-见过,因此只需提供参考即可
    • 输入list-build(1)指令

这确实会使事情变慢,并且可能会产生较大的泡菜,因此pickle有一个fast选项可以不传递便笺。如果使用该选项,您将得到一个RecursionError。而且该选项仍然被弃用。如文档所述,获取较小的泡菜的方法是正常制作泡菜,然后在其上调用optimize,这会过滤掉创建的所有不必要的PUT。


当然,真正的泡菜不会倾倒1list-build(2)之类的东西,但实际上并没有什么不同。您可以使用pickletools模块来查看其真正产生的结果:

    0: \x80 PROTO      3
    2: ]    EMPTY_LIST
    3: q    BINPUT     0
    5: (    MARK
    6: K        BININT1    1
    8: K        BININT1    1
   10: e        APPENDS    (MARK at 5)
   11: .    STOP

如您所见,1基本上只是BININT1 1,而且它并没有打扰共享引用,因为1是一个与引用一样小的对象。将会。但是,它实际上不是创建build-list(2),而是创建EMPTY_LIST,然后调用和APPENDS将元素添加到列表中。

如果您递归地包含自身列表:

    0: \x80 PROTO      3
    2: ]    EMPTY_LIST
    3: q    BINPUT     0
    5: h    BINGET     0
    7: a    APPEND
    8: .    STOP

…只是将列表添加到自身。用Python构建它的方式相同。