如果序列化中断,反序列化一定会失败吗? - Python
假设我在把一个pickle对象写入磁盘的时候发生了崩溃,导致写入没有完成。那么,当我尝试读取这个pickle对象的时候,一定会出现错误吗?还是说,有可能已经写出的那部分数据会被当作有效的pickle来处理,而错误就不会被发现呢?
5 个回答
把一个对象进行“打包”(pickling)会返回一个字符串对象,或者把这个字符串对象写入一个文件……这并不会改变原来的对象。如果在打包的过程中发生了“崩溃”(也就是出现了错误),那么结果就不会返回给调用者,所以你也没有什么可以尝试去“解包”(unpickle)的。此外,发生错误后,为什么你还想去解包一些没用的垃圾呢?
与其他答案不同,我认为我们可以有力地论证一个“pickle”是可以恢复的。简单来说,就是:“是的,一个不完整的pickle总是会导致异常。”
为什么我们能这么说呢?因为“pickle”格式实际上是一种小型的基于栈的语言。在这种语言中,你写的代码会一个接一个地把项目放到一个栈上,然后调用一个操作符来处理你积累的数据。而且,pickle必须以一个命令“.”结束,这个命令的意思是:“把现在栈底的项目取出来,作为这个pickle的值。”如果你的pickle提前结束,就不会有这个命令,你就会遇到EOF错误。
如果你想尝试恢复一些数据,你可能需要自己写一个解释器,或者在pickle.py中调用某个方法,绕过它在没有找到“.”时想要抛出EOFError的限制。需要记住的主要一点是,在大多数基于栈的语言中,大的数据结构是“反向构建”的:首先你把很多小字符串或数字放到栈上,然后你调用一个操作,告诉它“把这些放在一起形成一个列表”或者“抓取栈上的成对项目,做成一个字典”。所以,如果一个pickle被中断,你会发现栈里满是那些本来要构建的对象的部分,但你会缺少最后那段代码,它告诉你到底要用这些部分构建什么。
这是对S. Lott回答的进一步发展,我的建议是:在你的数据后面加一个哈希值或校验和,在再次解压之前检查一下。
这里有一个简单的实现,叫做safepickle/safeunpickle,展示了你如何在被压缩的数据中加上一个哈希值(在这个例子中是一个很强的加密哈希):
import hashlib
import cPickle as pickle
_HASHLEN = 20
def safepickle(obj):
s = pickle.dumps(obj)
s += hashlib.sha1(s).digest()
return s
def safeunpickle(pstr):
data, checksum = pstr[:-_HASHLEN], pstr[-_HASHLEN:]
if hashlib.sha1(data).digest() != checksum:
raise ValueError("Pickle hash does not match!")
return pickle.loads(data)
l = range(20)
p = safepickle(l)
new_l = safeunpickle(p)
print new_l == l
这个方法是为了确保你解压出来的数据和之前压缩并写入磁盘的数据是匹配的,但当然它并不能防止不同的压缩数据混淆或恶意攻击。
(这个方法可以推广到任何完整文件数据的模式,比如safe_write_file
和safe_read_file
。)