Python pickle 协议选择?
我正在使用Python 2.7,想要把一个对象保存到文件里。我想知道不同的pickle协议之间到底有什么区别。
import numpy as np
import pickle
class Data(object):
def __init__(self):
self.a = np.zeros((100, 37000, 3), dtype=np.float32)
d = Data()
print("data size: ", d.a.nbytes / 1000000.0)
print("highest protocol: ", pickle.HIGHEST_PROTOCOL)
pickle.dump(d, open("noProt", "w"))
pickle.dump(d, open("prot0", "w"), protocol=0)
pickle.dump(d, open("prot1", "w"), protocol=1)
pickle.dump(d, open("prot2", "w"), protocol=2)
out >> data size: 44.4
out >> highest protocol: 2
然后我发现保存的文件在磁盘上占用的大小不同:
noProt
: 177.6MBprot0
: 177.6MBprot1
: 44.4MBprot2
: 44.4MB
我知道prot0
是一个人类可读的文本文件,所以我不想用它。我猜协议0是默认的选项。
我想知道协议1和协议2之间有什么区别,为什么我应该选择其中一个而不是另一个?
使用pickle
和cPickle
哪个更好呢?
2 个回答
对于使用Python 3的人来说,从Python 3.5开始,有五种不同的协议可以选择:
现在有5种不同的协议可以用来进行数据的“序列化”(也就是把数据变成可以存储或传输的格式)。使用的协议版本越高,读取生成的序列化文件所需的Python版本就越新 [文档]:
协议版本0是最早的“人类可读”协议,能够与早期版本的Python兼容。
协议版本1是一种旧的二进制格式,也能与早期版本的Python兼容。
- 协议版本2是在Python 2.3中引入的。它对新式类的序列化效率更高。有关协议2带来的改进,可以参考PEP 307。
- 协议版本3是在Python 3.0中增加的。它明确支持字节对象,并且无法被Python 2.x反序列化。这是默认协议,也是当需要与其他Python 3版本兼容时推荐使用的协议。
- 协议版本4是在Python 3.4中增加的。它支持非常大的对象,能够序列化更多种类的对象,并且对数据格式进行了优化。有关协议4带来的改进,可以参考PEP 3154。
- 协议版本5是在Python 3.8中增加的。它支持带外数据,并加快了带内数据的处理速度。有关协议5带来的改进,可以参考PEP 574。
一般来说,你应该使用与目标兼容的最高协议。如果你希望与Python 2兼容,那么协议版本2是个不错的选择;如果希望与所有Python版本兼容,那么版本1也可以。如果你不在乎兼容性,使用pickle.HIGHEST_PROTOCOL
会自动选择你当前Python版本支持的最高协议。
另外,在Python 3中,导入pickle
时会自动导入C语言实现的版本。
还有一点需要注意的是,协议3和4默认使用字符串的unicode编码,而早期的协议则不使用。因此在Python 3中,如果你加载一个在Python 2中序列化的文件,可能需要明确指定编码才能正确加载。
使用最新的协议,这样可以支持你想要的最低Python版本来读取数据。更新的协议版本支持新的语言特性,并且包含了一些优化。
目前有6种不同的协议可以用于数据的序列化(也就是“腌制”)。使用的协议版本越高,读取生成的pickle所需的Python版本就越新。
- 协议版本0是最初的“人类可读”协议,向后兼容早期的Python版本。
- 协议版本1是一个旧的二进制格式,也兼容早期的Python版本。
- 协议版本2是在Python 2.3中引入的。它对新式类的序列化效率更高。有关协议2带来的改进,请参考PEP 307。
- 协议版本3是在Python 3.0中添加的。它明确支持
bytes
对象,并且不能被Python 2.x反序列化。这是Python 3.0到3.7的默认协议。- 协议版本4是在Python 3.4中添加的。它支持非常大的对象,能够序列化更多种类的对象,并且进行了一些数据格式的优化。从Python 3.8开始,它成为默认协议。有关协议4带来的改进,请参考PEP 3154。
- 协议版本5是在Python 3.8中添加的。它支持带外数据,并加快了带内数据的处理速度。有关协议5带来的改进,请参考PEP 574。
来自[pickle.Pickler(...)
类部分](
可选的protocol参数是一个整数,告诉序列化器使用指定的协议;支持的协议范围是0到
HIGHEST_PROTOCOL
。如果没有指定,默认使用DEFAULT_PROTOCOL
。如果指定了负数,则选择HIGHEST_PROTOCOL
。
所以,当你想要支持Python 3.4或更新版本来加载序列化的数据时,选择协议4。如果你还需要支持Python 2.7,选择协议2,特别是当你使用从object
(新式类)派生的自定义类时(现在的现代代码几乎都是这样)。
不过,如果你需要与其他Python版本交换序列化数据,或者需要保持与旧版本Python的兼容性,最简单的办法就是使用你能找到的最高协议版本:
with open("prot2", 'wb') as pfile:
pickle.dump(d, pfile, protocol=pickle.HIGHEST_PROTOCOL)
pickle.HIGHEST_PROTOCOL
总是适合当前的Python版本。因为这是一个二进制格式,确保使用'wb'
作为文件模式!
Python 3不再区分cPickle
和pickle
,在使用Python 3时总是使用pickle
。它在底层使用了编译的C扩展。
如果你还在使用Python 2,那么cPickle
和pickle
大部分是兼容的,主要的区别在于提供的API。对于大多数使用场景,使用cPickle
就可以了;它更快。再次引用文档:
首先,
cPickle
的速度可以比pickle快1000倍,因为前者是用C实现的。其次,在cPickle
模块中,调用的Pickler()
和Unpickler()
是函数,而不是类。这意味着你不能用它们来派生自定义的序列化和反序列化子类。大多数应用程序不需要这个功能,应该能从cPickle
模块显著提高的性能中受益。