为什么以下代码在作为脚本运行时会产生错误?在交互式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.')
答案 0 :(得分:2)
如果替换
行val1 = dict(fooblah=[], xy=[])
与
exec "val1 = dict(fooblah=[], xy=[])"
然后断言再次传递。
为什么?答案在于cPickle的奥秘。它具有一个优化,可以查看某些对象是否具有小于2的引用计数器,并且在这种情况下避免使用几个字节(通常用于检测相同可能大的字符串的周期或多次出现)。这是关于字符串对象“fooblah”和“xy”。在exec
的情况下或者以交互方式运行时,在您进行pickle时,留给字符串的唯一引用都在字典中;引用计数器为1,因此cPickle避免了几个字节。但是如果你把这个例子写成一个模块,那么那个时候模块仍处于活动状态,它会保留对用作常量的字符串的另一个引用。
编辑澄清:我们第二次腌制,我们将挑选一个字典,其中总是有来自unpickling的新密钥 - 引用计数器1.因此,当且仅当也是第一次引用计数器1的键。
答案 1 :(得分:1)
似乎是由cPickle
引起的,因为使用普通的pickle
不会发生这种情况(我能够重现您的错误)。
这就是为什么,等级1 ......我会继续研究,因为这是一个有趣的发现!
更新
cPickle文档(特别是脚注)保证对象始终/读/正确但不保证(或保留)序列化数据始终相等。可能不是意料之外的行为,但值得注意。