cPickle - 不同的结果腌制同一个对象

时间:2011-09-21 14:30:58

标签: python serialization pickle

是否有人能够在此 code snippet testLookups()下解释评论?

我已经运行了代码,确实评论sais是真的。但是我想理解为什么它是真的,即为什么cPickle根据它的引用方式为同一个对象输出不同的值。

它与引用计数有什么关系吗?如果是这样,那不是某种错误 - 即腌制和反序列化的对象会有异常高的引用计数,实际上永远不会收集垃圾?

2 个答案:

答案 0 :(得分:9)

无法保证看似相同的物体会产生相同的泡菜串。

pickle协议是虚拟机,pickle字符串是该虚拟机的程序。对于给定的对象,存在多个将完全重建该对象的pickle字符串(= program)。

举一个例子:

>>> from cPickle import dumps
>>> t = ({1: 1, 2: 4, 3: 6, 4: 8, 5: 10}, 'Hello World', (1, 2, 3, 4, 5), [1, 2, 3, 4, 5])
>>> dumps(({1: 1, 2: 4, 3: 6, 4: 8, 5: 10}, 'Hello World', (1, 2, 3, 4, 5), [1, 2, 3, 4, 5]))
"((dp1\nI1\nI1\nsI2\nI4\nsI3\nI6\nsI4\nI8\nsI5\nI10\nsS'Hello World'\np2\n(I1\nI2\nI3\nI4\nI5\ntp3\n(lp4\nI1\naI2\naI3\naI4\naI5\nat."
>>> dumps(t)
"((dp1\nI1\nI1\nsI2\nI4\nsI3\nI6\nsI4\nI8\nsI5\nI10\nsS'Hello World'\n(I1\nI2\nI3\nI4\nI5\nt(lp2\nI1\naI2\naI3\naI4\naI5\natp3\n."

两个pickle字符串在使用p操作码时有所不同。操作码采用一个整数参数,其功能如下:

  name='PUT'    code='p'   arg=decimalnl_short

  Store the stack top into the memo.  The stack is not popped.

  The index of the memo location to write into is given by the newline-
  terminated decimal string following.  BINPUT and LONG_BINPUT are
  space-optimized versions.

简而言之,这两个泡菜串基本相同。

我没有试图找出生成的操作码差异的确切原因。这很可能与被序列化对象的引用计数有关。然而,很明显,像这样的差异对重建的物体没有影响。

答案 1 :(得分:6)

正在查看来自cPickle来源的参考计数:

if (Py_REFCNT(args) > 1) {
    if (!( py_ob_id = PyLong_FromVoidPtr(args)))
        goto finally;

    if (PyDict_GetItem(self->memo, py_ob_id)) {
        if (get(self, py_ob_id) < 0)
            goto finally;

        res = 0;
        goto finally;
    }
}

pickle协议必须处理对同一对象的多个引用的pickle。为了防止在depickled时复制对象,它使用备忘录。备忘录基本上将索引映射到各种对象。 pickle中的PUT(p)操作码将当前对象存储在此备忘录字典中。

但是,如果只有一个对象的引用,则没有理由将它存储在备忘录中,因为它不可能再次引用它,因为它只有一个引用。因此,cPickle代码在此时检查引用计数以进行一点优化。

是的,它的引用很重要。但不是那不是问题。未打开的对象将具有正确的引用计数,当引用计数为1时,它只会产生稍短的pickle。

现在,我不知道你在做什么,你关心这个。但是你真的不应该认为酸洗相同的物体总会给你相同的结果。如果没有别的,我希望字典能给你带来问题,因为键的顺序是不确定的。除非你有python文档,保证每次强烈推荐你的pickle都不相同。