我浏览了关于pickle (python pickle)的文档,在与元帅的比较中发现,在对一个对象进行多次引用的情况下,pickle只会序列化该对象的一个实例,而所有其他引用都是指向序列化对象的主副本。
但是我想知道pickle如何在递归对象的情况下序列化对象。
答案 0 :(得分:0)
如果您已经了解了它是如何用指向主服务器的指针替换重复引用的,那么递归对象实际上没有什么不同,除了一个小的改动。
让我们仅用一些简单的示例,通过使用pickle实际功能的简化版本。
要转储[1, 1]
,您需要:
如果保留所见元素的备忘录字典,则可以:
id([1, 1])
(看不见),因此进入列表
id(1)
(看不见),因此转储1,并将其添加到memo
id(1)
-已经存在,因此只需转储引用即可现在,如果我们有类似lst = []; lst.append(lst)
的东西并尝试转储该怎么办?
id([...])
(看不见),因此进入列表
id([...])
-未显示,因此进入列表…哎呀,这将击中RecursionError
。
因此,您要做的是,在实际发出构建列表的代码之前,将id([...])
的目标添加到备忘中。没关系;参考文献只需要知道位置,而不是那里的位置。所以:
这确实会使事情变慢,并且可能会产生较大的泡菜,因此pickle
有一个fast
选项可以不传递便笺。如果使用该选项,您将得到一个RecursionError
。而且该选项仍然被弃用。如文档所述,获取较小的泡菜的方法是正常制作泡菜,然后在其上调用optimize
,这会过滤掉创建的所有不必要的PUT。
当然,真正的泡菜不会倾倒1
和list-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构建它的方式相同。