Python pickle 协议选择?

90 投票
2 回答
104949 浏览
提问于 2025-04-18 06:07

我正在使用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.6MB
  • prot0: 177.6MB
  • prot1: 44.4MB
  • prot2: 44.4MB

我知道prot0是一个人类可读的文本文件,所以我不想用它。我猜协议0是默认的选项。

我想知道协议1和协议2之间有什么区别,为什么我应该选择其中一个而不是另一个?

使用picklecPickle哪个更好呢?

2 个回答

33

对于使用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中序列化的文件,可能需要明确指定编码才能正确加载。

96

使用最新的协议,这样可以支持你想要的最低Python版本来读取数据。更新的协议版本支持新的语言特性,并且包含了一些优化。

来自pickle模块数据格式文档

目前有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不再区分cPicklepickle,在使用Python 3时总是使用pickle。它在底层使用了编译的C扩展。

如果你还在使用Python 2,那么cPicklepickle大部分是兼容的,主要的区别在于提供的API。对于大多数使用场景,使用cPickle就可以了;它更快。再次引用文档

首先,cPickle的速度可以比pickle快1000倍,因为前者是用C实现的。其次,在cPickle模块中,调用的Pickler()Unpickler()是函数,而不是类。这意味着你不能用它们来派生自定义的序列化和反序列化子类。大多数应用程序不需要这个功能,应该能从cPickle模块显著提高的性能中受益。

撰写回答