4

为什么以下代码在作为脚本运行时会产生错误?在交互式 shell(剪切和粘贴)中运行时不会产生错误。

import cPickle as pickle

val1 = dict(fooblah=[], xy=[])
pickval1 = pickle.dumps(val1, protocol=2)

val2 = pickle.loads(pickval1)
assert val1 == val2

pickval2 = pickle.dumps(val2, protocol=2)
assert pickval1 == pickval2, (pickval1, pickval2)

泡菜的区别如下:

$ python /tmp/picklefun.py
Traceback (most recent call last):
  File "/tmp/picklefun.py", line 10, in <module>
    assert pickval1 == pickval2, (pickval1, pickval2)
AssertionError: ('\x80\x02}q\x01(U\x07fooblahq\x02]U\x02xyq\x03]u.',
                 '\x80\x02}q\x01(U\x07fooblah]U\x02xy]u.')
4

2 回答 2

2

如果换行

val1 = dict(fooblah=[], xy=[])

exec "val1 = dict(fooblah=[], xy=[])"

然后断言再次通过。

为什么??答案深藏在 cPickle 的奥秘中。它有一个优化,查看某些对象的引用计数器是否小于 2,并在这种情况下避免几个字节(通常用于检测循环或同一可能大字符串的多次出现)。这是关于字符串对象“fooblah”和“xy”。在交互运行的情况下exec或运行时,当您腌制时,对字符串的唯一引用就在字典中;引用计数器为 1,因此 cPickle 避免了几个字节。但是如果你把这个例子写成一个模块,那么这个模块在那个时候仍然是活跃的,并且它保持另一个对用作常量的字符串的引用。

编辑澄清:我们第二次腌制时,我们将腌制一个字典,该字典总是具有来自 unpickling 的新键 - 引用计数器 1。因此,当且仅当第一次引用计数器 1 的键也是如此时,断言才会通过.

于 2013-05-08T08:10:22.477 回答
1

似乎是cPickle因为它不会发生使用普通旧的pickle(我能够重现您的错误)。

这就是为什么,1 级...我会继续研究,因为这是一个有趣的发现!

更新:

cPickle 文档(尤其是脚注)保证对象将始终正确/读取/,但不保证(或保留)序列化数据始终相等。可能不是意外的行为,但值得注意。

http://docs.python.org/2/library/pickle.html#module-cPickle

于 2013-05-07T23:19:53.380 回答