pickle
该pickle
模块为序列化和反序列化Python对象结构实现了一个基本但强大的算法。“Pickling”是将Python对象层次结构转换为字节流的过程,“unpickling”是相反的操作,即字节流转换回对象层次结构。Pickling(或取消)也被称为“序列化”,“编组”,或“扁平化”,但是,为避免混淆,这里使用的术语是“酸洗”和“取消”。
本文档描述了pickle
模块和cPickle
模块。
警告
该pickle
模块对于错误或恶意构建的数据不安全。切勿取消从不可信或未经认证的来源收到的数据。
1.与其他Python模块的关系
该pickle
模块有一个称为cPickle
模块的优化堂兄。顾名思义,cPickle
就是用C编写的,所以它的速度可以比C快1000倍pickle
。但是它不支持Pickler()
和Unpickler()
类的子类化,因为在cPickle
这些函数中,不是类。大多数应用程序不需要此功能,并且可以从改进的性能中受益cPickle
。除此之外,两个模块的接口几乎完全相同; 本手册介绍了通用接口,并在必要时指出了不同之处。在下面的讨论中,我们使用术语“泡菜”共同描述pickle
和cPickle
模块。
两个模块产生的数据流保证可以互换。
Python有一个更原始的序列化模块marshal
,但通常pickle
应该是序列化Python对象的首选方式。marshal
主要是为了支持Python的.pyc
文件。
该pickle
模块与以下marshal
几个重要方面有所不同:
- 该
pickle
模块跟踪它已经序列化的对象,以便以后对同一对象的引用不会再次序列化。marshal
不这样做。这对递归对象和对象共享都有影响。递归对象是包含对自己的引用的对象。这些不是由编组处理的,实际上,尝试编组递归对象会导致Python解释器崩溃。如果在被序列化的对象层次结构中的不同位置存在对同一对象的多个引用,则会发生对象共享。pickle
只存储一次这样的对象,并确保所有其他引用指向主副本。共享对象保持共享,这对于可变对象非常重要。
marshal
不能用于序列化用户定义的类及其实例。pickle
可以透明地保存和恢复类实例,但类定义必须是可导入的,并且与存储对象时位于同一模块中。
- 该
marshal
序列化格式是不能保证整个Python版本移植。因为它的主要工作是支持.pyc
文件,所以Python实现者保留在需要时以非向后兼容方式更改序列化格式的权利。该pickle
序列化格式是保证不同的Python版本向后兼容。
请注意,序列化是比持久性更原始的概念; 虽然pickle
读取和写入文件对象,但它不处理命名持久对象的问题,也不处理并发访问持久对象的(更复杂的)问题。该pickle
模块可以将复杂对象转换为字节流,并且可以将字节流转换为具有相同内部结构的对象。也许对这些字节流最明显的做法是将它们写入文件,但也可以将它们发送到网络或将它们存储在数据库中。该模块shelve
提供了一个简单的界面,可以在DBM样式的数据库文件上腌制和取消对象。
2.数据流格式
所使用的数据格式pickle
是Python特有的。这具有如下优点:不存在由诸如XDR的外部标准(其不能表示指针共享)施加的限制; 然而这意味着非Python程序可能无法重构pickled Python对象。
默认情况下,pickle
数据格式使用可打印的ASCII表示。这比二进制表示稍大一些。使用可打印ASCII(以及其他pickle
表示形式的其他特征)的一大优点是,出于调试或恢复的目的,人们可以使用标准文本编辑器阅读腌制文件。
目前有3种不同的协议可用于pickling。
- 协议版本0是原始的ASCII协议,并且与早期版本的Python向后兼容。
- 协议版本1是旧的二进制格式,它也与早期版本的Python兼容。
- 协议版本2是在Python 2.3中引入的。它提供了更有效的酸洗新式课程。
有关更多信息,请参阅PEP 307。
如果一个协议没有指定,协议0被使用。如果协议被指定为负值,或HIGHEST_PROTOCOL
将使用可用的最高协议版本。
版本2.3中更改:引入了协议参数。
可以通过指定协议版本> = 1 来选择稍微更高效的二进制格式。
3.用法
要序列化对象层次结构,首先创建一个pickler,然后调用pickler的dump()
方法。为了反序列化数据流,首先创建一个unpickler,然后调用unpickler的load()
方法。该pickle
模块提供以下常数:
pickle.HIGHEST_PROTOCOL
可用的最高协议版本。该值可以作为协议值传递。
2.3版本的新功能。
注意
确保始终以二进制模式打开使用协议> = 1创建的pickle文件。对于旧的基于ASCII的pickle协议0,只要保持一致,就可以使用文本模式或二进制模式。
在二进制模式下使用协议0编写的pickle文件将包含单行换行符作为行终止符,因此在使用记事本或其他不支持此格式的编辑器中查看时看起来会很“滑稽”。
该pickle
模块提供以下功能,使酸洗过程更加方便:
pickle.dump(obj, file[, protocol])
将obj的pickle表示写入打开的文件对象文件。这相当于Pickler(file, protocol).dump(obj)
。
如果协议参数被省略,则使用协议0。如果协议被指定为负值,或者HIGHEST_PROTOCOL
将使用最高协议版本。
版本2.3中更改:引入了协议参数。
文件必须有一个write()
接受单个字符串参数的方法。因此它可以是一个为写入而打开的文件对象,一个StringIO
对象或符合此接口的任何其他自定义对象。
pickle.load(file)
从打开的文件对象文件中读取一个字符串,并将其解释为pickle数据流,重建并返回原始对象层次结构。这相当于Unpickler(file).load()
。
文件必须有两个方法,一个read()
采用整数参数的readline()
方法和一个不需要参数的方法。两种方法都应该返回一个字符串 因此,文件可以是为阅读而打开的文件对象,StringIO
对象或符合此界面的任何其他自定义对象。
该功能自动确定数据流是否以二进制模式写入。
pickle.dumps(obj[, protocol])
将对象的pickled表示形式返回为字符串,而不是将其写入文件。
如果协议参数被省略,则使用协议0。如果协议被指定为负值,或者HIGHEST_PROTOCOL
将使用最高协议版本。
在版本2.3中更改:添加了协议参数。
pickle.loads(string)
从字符串中读取一个pickled对象层次结构。字符串中超过pickle对象表示的字符将被忽略。
该pickle
模块还定义了三个例外:
exception pickle.PickleError
下面定义的其他例外的通用基类。这继承了Exception
。
exception pickle.PicklingError
当不可识别的对象传递给dump()
方法时引发此异常。
exception pickle.UnpicklingError
当取消对象时出现问题时会引发此异常。需要注意的是其他异常也可以取储存,包括(但不一定局限于)过程中引发的AttributeError
,EOFError
,ImportError
,和IndexError
。
该pickle
模块还导出两个可调用的[2],Pickler
并且Unpickler
:
class pickle.Pickler(file[, protocol])
这需要一个文件类对象,它将写入一个pickle数据流。
如果协议参数被省略,则使用协议0。如果协议被指定为负值,或者HIGHEST_PROTOCOL
将使用最高协议版本。
版本2.3中更改:引入了协议参数。
文件必须有一个write()
接受单个字符串参数的方法。因此,它可以是一个打开的文件对象,一个StringIO
对象或符合此接口的任何其他自定义对象。
Pickler
对象定义一个(或两个)公共方法:
dump(obj)
向构造函数中给出的打开的文件对象写一个腌制的obj表示形式。将使用二进制或ASCII格式,具体取决于传递给构造函数的协议参数的值。
clear_memo()
清除pickler的“备忘录”。备忘录是记录pickler已经看到的对象的数据结构,以便共享或递归对象通过引用而不是按值进行pickle。这种方法在重新使用pickler时很有用。
注意
在Python 2.3之前,clear_memo()
仅在创建的picker上可用cPickle
。在pickle
模块中,picklers有一个实例变量叫做memo
which是一个Python字典。因此,要清除pickle
模块拾取器的备忘录,您可以执行以下操作:
mypickler.memo.clear()
不需要支持较旧版本的Python的代码应该简单地使用clear_memo()
。
可以对dump()
同一个Pickler
实例的方法进行多次调用。然后这些必须匹配到load()
相应Unpickler
实例的方法的相同数量的调用。如果同一个对象被多次dump()
调用腌制,那么这个load()
将全部产生对同一个对象的引用。[3]
Unpickler
对象被定义为:
class pickle.Unpickler(file)
这需要一个类似文件的对象,它将从中读取一个pickle数据流。该类自动确定数据流是否以二进制模式写入,因此它不需要Pickler
工厂中的标志。
文件必须有两个方法,一个read()
采用整数参数的readline()
方法和一个不需要参数的方法。两种方法都应该返回一个字符串 因此,文件可以是为阅读而打开的文件对象,StringIO
对象或符合此界面的任何其他自定义对象。
Unpickler
对象有一个(或两个)公共方法:
load()
从构造函数中给出的打开文件对象中读取一个pickle对象表示形式,并返回其中指定的重构对象层次结构。
该方法自动确定数据流是否以二进制模式写入。
noload()
这就像load()
除了它实际上不创建任何对象。这主要用于查找可能在pickle数据流中引用的称为“持久性id”的东西。有关更多详细信息,请参见下面的pickle协议。
注意:该noload()
方法当前仅Unpickler
在使用该cPickle
模块创建的对象上可用。pickle
模块Unpickler
没有这个noload()
方法。
4.什么可以 pickled和unpickled?
以下类型可以被pickled:
None
,True
, andFalse
- 整数,长整数,浮点数,复数
- 正常和Unicode字符串
- 元组,列表,集合和仅包含可选对象的字典
- 函数定义在模块的顶层
- 在模块顶层定义的内置函数
- 在模块顶层定义的类
- 这些类的实例
__dict__
或调用的结果__getstate__()
是可挑选的(请参阅pickle协议的细节部分)。
尝试pickle unpicklable对象会引发PicklingError
异常; 发生这种情况时,可能已将未指定数量的字节写入底层文件。试图腌制一个高度递归的数据结构可能会超过最大递归深度,RuntimeError
在这种情况下会引发一次。你可以谨慎地提高这个限制sys.setrecursionlimit()
。
请注意,函数(内置的和用户定义的)由“完全限定”名称引用进行挑选,而不是按值进行。这意味着只有函数名称被腌渍,以及定义该函数的模块的名称。该函数的代码及其任何函数属性都不会被腌制。因此,定义模块必须可以在取消环境中导入,并且模块必须包含指定的对象,否则将引发异常。[4]
同样,类按名称引用进行挑选,因此在取消环境中适用相同的限制。请注意,没有任何类的代码或数据被腌制,因此在下面的示例中,attr
不会在unpickling环境中恢复class属性:
class Foo:
attr = 'a class attr'
picklestring = pickle.dumps(Foo)
这些限制是为什么必须在模块的顶层定义可调用的函数和类。
同样,当类实例被腌制时,他们的类的代码和数据不会随着它们一起被腌制。只有实例数据被腌制。这是有意完成的,因此您可以修复类中的错误或向类中添加方法,并仍然加载使用该类的早期版本创建的对象。如果您计划使用能够看到许多版本的类的长效对象,则可能需要在对象中添加版本号,以便可以通过类的__setstate__()
方法进行适当的转换。
5.pickle协议
本节介绍定义Pickler / unpickler和正在序列化的对象之间接口的“酸洗协议”。该协议为您定义,定制和控制对象如何序列化和反序列化提供了一种标准方法。本节中的描述不包括您可以使用的特定自定义设置,以使不受信任的pickle数据流更安全一些。有关更多详细信息,请参见子类化Unpicklers部分。
5.1.酸洗和取消正常的类实例
object.__getinitargs__()
当pickled类实例被取消选中时,__init__()
通常不调用它的方法。如果需要在__init__()
取消打开时调用该方法,则旧式类可以定义一个方法__getinitargs__()
,该方法应返回包含要传递给类构造函数的参数的元组(__init__()
例如)。该__getinitargs__()
方法在腌制时间被调用; 它返回的元组被包含在实例的pickle中。
object.__getnewargs__()
obj = C.__new__(C, *args)
其中ARGS是调用的结果而__getnewargs__()
原来的对象上; 如果不存在__getnewargs__()
,则假定一个空元组。
object.__getstate__()
课程可以进一步影响他们的实例如何腌制; 如果类定义了该方法__getstate__()
,则会调用该方法,并将返回状态作为实例的内容进行挑选,而不是实例字典的内容。如果没有__getstate__()
方法,则实例__dict__
被腌制。
object.__setstate__(state)
取消之后,如果类也定义了该方法__setstate__()
,那么将使用unpickled状态调用该方法。[5]如果没有__setstate__()
方法,pickled状态必须是一个字典,并且它的项目被分配给新实例的字典。如果一个类定义了__getstate__()
和__setstate__()
,状态对象不一定是字典,这些方法可以做他们想要的东西。[6]
Note
对于新样式类,如果__getstate__()
返回一个假值,则该__setstate__()
方法不会被调用。
注意
在在unpickle时,一些方法,如__getattr__()
,__getattribute__()
或__setattr__()
可在该实例调用。如果这些方法依赖于一些内部不变为真,则类型应该实现任一__getinitargs__()
或__getnewargs__()
建立这样的不变的; 否则,既__new__()
不会也__init__()
不会被调用。
5.2.酸洗和取消扩展类型
object.__reduce__()
当Pickler
遇到一个类型的对象时,它一无所知 - 例如扩展类型 - 它在两个地方寻找如何腌制它的提示。一种替代方案是对象实现一种__reduce__()
方法。如果提供,在酸洗时__reduce__()
将被调用,不带任何参数,并且它必须返回一个字符串或一个元组。
如果返回一个字符串,它就会命名一个全局变量,其内容被正常腌制。返回的字符串__reduce__()
应该是相对于其模块的对象的本地名称; pickle模块搜索模块名称空间以确定对象的模块。
当一个元组返回时,它的长度必须在2到5个元素之间。可选元素可以省略,None
也可以作为它们的值提供。这个元组的内容按照正常方式进行腌制,并且在取出时用于重建对象。每个元素的语义是:
- 可调用的对象,将被调用来创建该对象的初始版本。元组的下一个元素将为此可调用对象提供参数,随后的元素将提供附加的状态信息,随后将用它们来完全重构pickle数据。在unpickling环境中,这个对象必须是一个类,一个可调用的注册为“安全构造函数”(参见下文),或者它必须具有
__safe_for_unpickling__
一个真值的属性。否则,UnpicklingError
将在未开封的环境中提出。请注意,像往常一样,可调用本身是按名称腌制的。
- 可调用对象的参数元组。
在版本2.5中改变了:以前,这个论点也可以None
。
- 可选地,该对象的状态将按照
__setstate__()
Pickling和Unickling普通类实例中所述传递给对象的方法。如果该对象没有__setstate__()
方法,那么,如上所述,该值必须是一个字典,它将被添加到该对象的__dict__
。
- 可选地,迭代器(而不是序列)产生连续的列表项。这些列表项将被酸洗,并追加到使用任一对象
obj.append(item)
或obj.extend(list_of_items)
。这主要用于列表子类,但可以由其他类使用,只要它们具有相应的签名append()
并且extend()
具有适当的签名方法。(无论append()
或extend()
使用取决于哪泡菜协议版本被用作以及项目追加的次数,所以两者都必须被支持。)
- 可选地,一个迭代器(而不是一个序列)产生连续的字典项目,它们应该是表单的元组
(key, value)
。这些项目将被酸洗并存储到对象使用obj[key] = value
。这主要用于字典子类,但只要它们实现,可以由其他类使用__setitem__()
。
object.__reduce_ex__(protocol)
在实施时了解协议版本有时很有用__reduce__()
。这可以通过实现一个名为,__reduce_ex__()
而不是__reduce__()
。__reduce_ex__()
,当它存在时,被优先调用__reduce__()
(你仍然可以提供__reduce__()
向后兼容性)。该__reduce_ex__()
方法将使用单个整数参数(协议版本)进行调用。
这个object
类实现了__reduce__()
和__reduce_ex__()
; 然而,如果一个子类覆盖__reduce__()
但不是__reduce_ex__()
,__reduce_ex__()
实现检测到这一点并调用__reduce__()
。
__reduce__()
在要被腌制的对象上实现方法的另一种方法是向copy_reg
模块注册可调用对象。该模块为程序提供了一种注册用户定义类型的“简化函数”和构造函数的方法。约简函数具有与上述__reduce__()
方法相同的语义和接口,只不过它们是用一个参数调用的,这个对象是被腌制的。
如上所述,已注册的构造函数被视为“安全构造函数”,用于拆除目的。
5.3.酸洗和取出外部物体
为了获得对象持久性,pickle
模块支持对pickle数据流之外的对象的引用的概念。这些对象由“持久性id”引用,它只是可打印的ASCII字符的任意字符串。这些名称的解析不是由pickle
模块定义的; 它将把这个分辨率委托给pickler和unpickler上的用户定义函数。[7]
要定义外部持久性标识解析,您需要设置persistent_id
pickler对象的persistent_load
属性和unpickler对象的属性。
要pickle具有外部持久性id的对象,picker必须有一个自定义persistent_id()
方法,它将一个对象作为参数,并返回None
该对象的持久性id或该持久性id。当None
返回时,只需皮克勒泡菜对象为正常。当返回一个持久化的id字符串时,pickler会腌制该字符串以及一个标记,这样unpickler会将该字符串识别为持久性id。
要取消对外部对象的打击,unpickler必须具有一个自定义persistent_load()
函数,该函数采用持久性id字符串并返回引用的对象。
这是一个愚蠢的例子,可能会提供更多的信息:
import pickle
from cStringIO import StringIO
src = StringIO()
p = pickle.Pickler(src)
def persistent_id(obj):
if hasattr(obj, 'x'):
return 'the value %d' % obj.x
else:
return None
p.persistent_id = persistent_id
class Integer:
def __init__(self, x):
self.x = x
def __str__(self):
return 'My name is integer %d' % self.x
i = Integer(7)
print i
p.dump(i)
datastream = src.getvalue()
print repr(datastream)
dst = StringIO(datastream)
up = pickle.Unpickler(dst)
class FancyInteger(Integer):
def __str__(self):
return 'I am the integer %d' % self.x
def persistent_load(persid):
if persid.startswith('the value '):
value = int(persid.split()[2])
return FancyInteger(value)
else:
raise pickle.UnpicklingError, 'Invalid persistent id'
up.persistent_load = persistent_load
j = up.load()
print j
在cPickle
模块中,unpickler的persistent_load
属性也可以设置为一个Python列表,在这种情况下,当unpickler到达一个持久id时,持久id字符串将被简单地附加到这个列表中。这个功能的存在使得pickle数据流可以被“嗅探”而不需要真正实例化pickle中的所有对象。[8]设置persistent_load
为列表通常与noload()
Unpickler上的方法一起使用。
6.子类化Unpicklers
默认情况下,unpickling会导入它在pickle数据中找到的任何类。您可以准确地控制取消拨号的内容以及通过自定义取消拨号程序调用的内容。不幸的是,你究竟如何做到这一点,取决于你是使用pickle
还是不同cPickle
。[9]
在pickle
模块中,您需要派生一个子类Unpickler
,覆盖该load_global()
方法。load_global()
应该从pickle数据流中读取两行,其中第一行是包含类的模块的名称,第二行是实例类的名称。然后它查找类,可能导入模块并挖掘属性,然后将它找到的内容追加到unpickler的堆栈中。之后,这个类将被分配给__class__
一个空类的属性,作为魔术般创建一个实例而不调用它的类的一种方式__init__()
。你的工作(如果你选择接受它)将是有的load_global()
推到unpickler的堆栈,一个已知的安全版本的任何你认为可以安全取出的类。由你来制作这样的课程。或者,如果您想禁止所有取消打开实例,则可能会出现错误。如果这听起来像一个黑客,你说得对。参考源代码来完成这项工作。
事情有点清洁cPickle
,但不是太多。要控制取消选中的对象,可以将unpickler的find_global
属性设置为一个函数或None
。如果是的None
话,任何试图解除实例的尝试都会引发一次UnpicklingError
。如果它是一个函数,那么它应该接受一个模块名称和一个类名,并返回相应的类对象。它负责查找课程并执行任何必要的导入操作,并且可能会引发错误,以防止课堂实例被取消。
这个故事的寓意是你应该非常小心你的应用程序取消选择的字符串的来源。
7.例子
对于最简单的代码,使用dump()
和load()
函数。请注意,自引用列表已被酸洗并正确恢复。
import pickle
data1 = {'a': [1, 2.0, 3, 4+6j],
'b': ('string', u'Unicode string'),
'c': None}
selfref_list = [1, 2, 3]
selfref_list.append(selfref_list)
output = open('data.pkl', 'wb')
# Pickle dictionary using protocol 0.
pickle.dump(data1, output)
# Pickle the list using the highest protocol available.
pickle.dump(selfref_list, output, -1)
output.close()
以下示例读取所产生的腌制数据。当读取含有腌菜的文件时,应该以二进制模式打开文件,因为您无法确定是否使用了ASCII或二进制格式。
import pprint, pickle
pkl_file = open('data.pkl', 'rb')
data1 = pickle.load(pkl_file)
pprint.pprint(data1)
data2 = pickle.load(pkl_file)
pprint.pprint(data2)
pkl_file.close()
下面是一个更大的例子,展示了如何修改一个类的酸洗行为。本TextReader
类打开一个文本文件,并返回每一次它的行号和行内容,readline()
方法被调用。如果一个TextReader
实例被腌制,除文件对象成员之外的所有属性都将被保存。当实例取消选中时,将重新打开该文件,并从最后一个位置继续读取。该__setstate__()
和__getstate__()
方法来实现此行为。
#!/usr/local/bin/python
class TextReader:
"""Print and number lines in a text file."""
def __init__(self, file):
self.file = file
self.fh = open(file)
self.lineno = 0
def readline(self):
self.lineno = self.lineno + 1
line = self.fh.readline()
if not line:
return None
if line.endswith("\n"):
line = line[:-1]
return "%d: %s" % (self.lineno, line)
def __getstate__(self):
odict = self.__dict__.copy() # copy the dict since we change it
del odict['fh'] # remove filehandle entry
return odict
def __setstate__(self, dict):
fh = open(dict['file']) # reopen file
count = dict['lineno'] # read from file...
while count: # until line count is restored
fh.readline()
count = count - 1
self.__dict__.update(dict) # update attributes
self.fh = fh # save the file object
示例用法可能如下所示:
>>> import TextReader
>>> obj = TextReader.TextReader("TextReader.py")
>>> obj.readline()
'1: #!/usr/local/bin/python'
>>> obj.readline()
'2: '
>>> obj.readline()
'3: class TextReader:'
>>> import pickle
>>> pickle.dump(obj, open('save.p', 'wb'))
如果你想看到它pickle
在Python进程中工作,在继续之前启动另一个Python会话。接下来可能发生在同一流程或新流程之后。
>>> import pickle
>>> reader = pickle.load(open('save.p', 'rb'))
>>> reader.readline()
'4: """Print and number lines in a text file."""'
本文档系腾讯云开发者社区成员共同维护,如有问题请联系 cloudcommunity@tencent.com