11.1. pickle — Python 对象序列化 — Python 文档
11.1. 泡菜 — Python 对象序列化
pickle 模块实现了一个基本但功能强大的算法,用于序列化和反序列化 Python 对象结构。 “pickling”是将 Python 对象层次结构转换为字节流的过程,“unpickling”是逆操作,将字节流转换回对象层次结构。 酸洗(和取消酸洗)也称为“序列化”、“编组”、1 或“扁平化”,但是,为避免混淆,此处使用的术语是“酸洗”和“取消酸洗”。
11.1.1. 与其他 Python 模块的关系
pickle 模块有一个优化的表亲,称为 cPickle 模块。 顾名思义,cPickle 是用 C 编写的,所以它的速度可以比 pickle 快 1000 倍。 然而,它不支持 Pickler() 和 Unpickler() 类的子类化,因为在 cPickle 中,这些是函数,而不是类。 大多数应用程序不需要此功能,并且可以从 cPickle 的改进性能中受益。 除此之外,两个模块的接口几乎相同; 本手册中描述了通用接口,并在必要时指出了不同之处。 在下面的讨论中,我们使用术语“pickle”来统一描述 pickle 和 cPickle 模块。
两个模块产生的数据流保证可以互换。
Python 有一个更原始的序列化模块,称为 marshal,但总的来说,pickle 应该始终是序列化 Python 对象的首选方式。 marshal 的存在主要是为了支持 Python 的 .pyc
文件。
pickle 模块跟踪它已经序列化的对象,以便以后对同一对象的引用不会再次序列化。 marshal 不这样做。
这对递归对象和对象共享都有影响。 递归对象是包含对自身的引用的对象。 这些不是由 marshal 处理的,实际上,尝试 marshal 递归对象会使您的 Python 解释器崩溃。 当在被序列化的对象层次结构中的不同位置对同一对象有多个引用时,就会发生对象共享。 pickle 仅存储此类对象一次,并确保所有其他引用都指向主副本。 共享对象保持共享状态,这对于可变对象非常重要。
marshal 不能用于序列化用户定义的类及其实例。 pickle 可以透明地保存和恢复类实例,但是类定义必须是可导入的,并且与存储对象时存在于同一模块中。
marshal 序列化格式不能保证跨 Python 版本可移植。 因为 Python 的主要工作是支持
.pyc
文件,所以 Python 实现者保留在需要时以非向后兼容的方式更改序列化格式的权利。 pickle 序列化格式保证向后兼容跨 Python 版本。
请注意,序列化是一个比持久化更原始的概念; pickle虽然读写文件对象,但它没有处理命名持久对象的问题,也没有处理持久对象的并发访问(甚至更复杂)的问题。 pickle 模块可以将复杂对象转换为字节流,也可以将字节流转换为具有相同内部结构的对象。 也许对这些字节流做的最明显的事情是将它们写入文件,但也可以通过网络发送它们或将它们存储在数据库中。 模块shelve 提供了一个简单的接口来pickle 和unpickle DBM 样式数据库文件上的对象。
11.1.2. 数据流格式
pickle 使用的数据格式是 Python 特定的。 这样做的好处是没有XDR(不能代表指针共享)等外部标准强加的限制; 然而,这意味着非 Python 程序可能无法重建腌制的 Python 对象。
默认情况下,pickle 数据格式使用可打印的 ASCII 表示。 这比二进制表示略多。 使用可打印的 ASCII(以及 pickle 表示的其他一些特征)的一大优点是,出于调试或恢复目的,人类可以使用标准文本编辑器读取 pickle 文件。
目前有 3 种不同的协议可用于酸洗。
- 协议版本 0 是原始 ASCII 协议,向后兼容早期版本的 Python。
- 协议版本 1 是旧的二进制格式,它也与早期版本的 Python 兼容。
- 协议版本 2 是在 Python 2.3 中引入的。 它为 新式类 提供了更有效的酸洗。
有关更多信息,请参阅 PEP 307。
如果未指定 协议 ,则使用协议 0。 如果 protocol 指定为负值或 HIGHEST_PROTOCOL,将使用可用的最高协议版本。
2.3 版变更: 引入了 协议 参数。
可以通过指定 protocol version >= 1 来选择一种稍微更有效的二进制格式。
11.1.3. 用法
要序列化对象层次结构,首先创建一个pickler,然后调用pickler 的dump() 方法。 要反序列化数据流,首先要创建一个 unpickler,然后调用 unpickler 的 load() 方法。 pickle 模块提供以下常量:
- pickle.HIGHEST_PROTOCOL
可用的最高协议版本。 此值可以作为 协议 值传递。
2.3 版中的新功能。
笔记
确保始终以二进制模式打开使用协议 >= 1 创建的 pickle 文件。 对于旧的基于 ASCII 的 pickle 协议 0,只要保持一致,您可以使用文本模式或二进制模式。
以二进制模式使用协议 0 编写的 pickle 文件将包含单独的换行符作为行终止符,因此在记事本或其他不支持此格式的编辑器中查看时会看起来“有趣”。
pickle模块提供了以下功能,让pickle过程更加方便:
- pickle.dump(obj, file[, protocol])
将 obj 的腌制表示写入打开的文件对象 file。 这相当于
Pickler(file, protocol).dump(obj)
。如果省略 protocol 参数,则使用协议 0。 如果 protocol 指定为负值或 HIGHEST_PROTOCOL,将使用最高协议版本。
2.3 版变更: 引入了 协议 参数。
file 必须有一个接受单个字符串参数的
write()
方法。 因此,它可以是为写入而打开的文件对象、StringIO 对象或任何其他符合此接口的自定义对象。
- pickle.load(file)
从打开的文件对象file中读取一个字符串并将其解释为pickle数据流,重构并返回原始对象层次结构。 这相当于
Unpickler(file).load()
。file 必须有两个方法,一个采用整数参数的
read()
方法,以及不需要参数的 readline() 方法。 这两种方法都应该返回一个字符串。 因此 file 可以是打开读取的文件对象,StringIO 对象,或任何其他符合此接口的自定义对象。该函数自动判断数据流是否以二进制方式写入。
- pickle.dumps(obj[, protocol])
将对象的腌制表示作为字符串返回,而不是将其写入文件。
如果省略 protocol 参数,则使用协议 0。 如果 protocol 指定为负值或 HIGHEST_PROTOCOL,将使用最高协议版本。
2.3 版更改: 添加了 协议 参数。
- pickle.loads(string)
- 从字符串中读取腌制对象层次结构。 字符串中经过腌制对象表示的字符将被忽略。
pickle 模块还定义了三个异常:
- exception pickle.PickleError
- 下面定义的其他异常的公共基类。 这继承自
Exception
。
- exception pickle.PicklingError
- 当不可选择的对象传递给 dump() 方法时,会引发此异常。
- exception pickle.UnpicklingError
- 当取消对象时出现问题时会引发此异常。 请注意,在 unpickling 期间也可能引发其他异常,包括(但不一定限于)
AttributeError
、EOFError
、ImportError
和IndexError
。
pickle 模块还导出了两个可调用对象 2、Pickle 和 Unpickler:
- class pickle.Pickler(file[, protocol])
这需要一个类似文件的对象,它将写入一个pickle 数据流。
如果省略 protocol 参数,则使用协议 0。 如果 protocol 指定为负值或 HIGHEST_PROTOCOL,将使用最高协议版本。
2.3 版变更: 引入了 协议 参数。
file 必须有一个接受单个字符串参数的
write()
方法。 因此,它可以是一个打开的文件对象、一个 StringIO 对象或任何其他符合此接口的自定义对象。Pickler 对象定义了一个(或两个)公共方法:
- dump(obj)
将 obj 的腌制表示写入构造函数中给出的打开文件对象。 将使用二进制或 ASCII 格式,具体取决于传递给构造函数的 protocol 参数的值。
- clear_memo()
清除pickler的“备忘录”。 memo 是一种数据结构,它记住pickler 已经看过哪些对象,以便共享或递归对象通过引用而不是通过值进行pickle。 此方法在重用pickler 时很有用。
笔记
在 Python 2.3 之前,clear_memo() 仅在由 cPickle 创建的pickler 上可用。 在 pickle 模块中,pickler 有一个名为
memo
的实例变量,它是一个 Python 字典。 因此,要清除 pickle 模块pickler 的备忘录,您可以执行以下操作:mypickler.memo.clear()
不需要支持旧版本 Python 的代码应该简单地使用 clear_memo()。
可以多次调用同一个 Picker 实例的 dump() 方法。 然后,这些必须与对相应 Unpickler 实例的 load() 方法的相同调用次数相匹配。 如果同一个对象被多个 dump() 调用腌制,load() 将全部产生对同一个对象的引用。 3
Unpickler 对象定义为:
- class pickle.Unpickler(file)
这需要一个类似文件的对象,它将从中读取一个 pickle 数据流。 此类自动确定数据流是否以二进制模式写入,因此它不需要像 Picker 工厂中的标志。
file 必须有两个方法,一个采用整数参数的
read()
方法,以及不需要参数的 readline() 方法。 这两种方法都应该返回一个字符串。 因此 file 可以是打开读取的文件对象,StringIO 对象,或任何其他符合此接口的自定义对象。Unpickler 对象有一个(或两个)公共方法:
- load()
从构造函数中给出的打开文件对象中读取腌制对象表示,并返回其中指定的重构对象层次结构。
该方法自动判断数据流是否以二进制方式写入。
11.1.4. 什么可以腌制和不腌制?
可以腌制以下类型:
None
、True
和False
- 整数、长整数、浮点数、复数
- 普通字符串和 Unicode 字符串
- 仅包含可picklable 对象的元组、列表、集合和字典
- 在模块顶层定义的函数
- 定义在模块顶层的内置函数
- 在模块顶层定义的类
- 其 __dict__ 或调用
__getstate__()
的结果是可pickle 的此类类的实例(有关详细信息,请参阅 pickle 协议 部分)。
尝试腌制不可腌制的对象将引发 PicklingError 异常; 发生这种情况时,可能已经将未指定数量的字节写入底层文件。 尝试腌制一个高度递归的数据结构可能会超过最大递归深度,在这种情况下会引发 RuntimeError
。 您可以使用 sys.setrecursionlimit() 小心地提高此限制。
请注意,函数(内置和用户定义的)由“完全限定”名称引用而不是值来腌制。 这意味着只腌制函数名称,以及定义函数的模块的名称。 函数的代码及其任何函数属性都不会被腌制。 因此定义模块必须在 unpickling 环境中可导入,并且模块必须包含命名对象,否则将引发异常。 4
类似地,类通过命名引用进行酸洗,因此在 unpickling 环境中适用相同的限制。 请注意,类的代码或数据均未经过酸洗,因此在以下示例中,类属性 attr
在 unpickling 环境中不会恢复:
class Foo:
attr = 'a class attr'
picklestring = pickle.dumps(Foo)
这些限制就是为什么必须在模块的顶层定义可腌制的函数和类。
类似地,当类实例被腌制时,它们的类的代码和数据不会与它们一起被腌制。 只有实例数据被腌制。 这是故意完成的,因此您可以修复类中的错误或向类添加方法,并且仍然加载使用早期版本的类创建的对象。 如果您计划拥有将看到一个类的多个版本的长期对象,那么在对象中放置一个版本号可能是值得的,以便可以通过类的 __setstate__()
方法进行适当的转换。
11.1.5. 泡菜协议
本节描述了“pickling 协议”,它定义了pickler/unpickler 和正在被序列化的对象之间的接口。 该协议为您提供了一种标准方式来定义、自定义和控制对象的序列化和反序列化方式。 本节中的描述不包括您可以用来使 unpickling 环境从不受信任的 pickle 数据流中稍微安全一些的特定自定义; 有关更多详细信息,请参阅 子类化 Unpicklers 部分。
11.1.5.1. 酸洗和取消酸洗普通类实例
- object.__getinitargs__()
- 当一个 pickled 类实例被 unpickled 时,它的 __init__() 方法通常是 not 调用。 如果需要在 unpickling 时调用 __init__() 方法,旧式类可以定义一个方法 __getinitargs__(),它应该返回一个 tuple ] 要传递给类构造函数的位置参数(例如 __init__())。 不支持关键字参数。 __getinitargs__() 方法在pickle 时被调用; 它返回的元组包含在实例的泡菜中。
- object.__getnewargs__()
新型类型可以提供用于协议 2 的 __getnewargs__() 方法。 如果类型在创建实例时建立了一些内部不变量,或者如果内存分配受到传递给该类型的 __new__() 方法的值的影响(就像元组一样),则需要实现此方法和字符串)。 new-style class
C
的实例是使用obj = C.__new__(C, *args)
其中 args 是对原始对象调用 __getnewargs__() 的结果; 如果没有 __getnewargs__(),则假定为空元组。
- object.__getstate__()
- 类可以进一步影响它们的实例如何被腌制; 如果类定义了 __getstate__() 方法,则调用该方法并将返回状态作为实例的内容进行腌制,而不是实例字典的内容。 如果没有 __getstate__() 方法,则实例的 __dict__ 被腌制。
- object.__setstate__(state)
在 unpickling 时,如果该类还定义了方法 __setstate__(),则以 unpickled 状态调用它。 5 如果没有 __setstate__() 方法,pickled 状态必须是一个字典,并且它的项被分配给新实例的字典。 如果一个类同时定义了 __getstate__() 和 __setstate__(),则状态对象不必是字典,这些方法可以做他们想做的事。 6
笔记
对于 new-style classes,如果 __getstate__() 返回 false 值,则不会调用 __setstate__() 方法。
笔记
在 unpickling 时,可能会在实例上调用一些方法,如 __getattr__()
、__getattribute__()
或 __setattr__()
。 如果这些方法依赖于某些内部不变量为真,则该类型应实现 __getinitargs__()
或 __getnewargs__()
来建立这样的不变量; 否则,__new__()
和 __init__()
都不会被调用。
11.1.5.2. Pickling 和 unpickling 扩展类型
- object.__reduce__()
当
Pickler
遇到一个它不知道的类型的对象时——比如扩展类型——它会在两个地方寻找如何处理它的提示。 一种替代方法是让对象实现 __reduce__() 方法。 如果提供,在酸洗时间 __reduce__() 将不带参数调用,并且它必须返回一个字符串或一个元组。如果返回一个字符串,它会命名一个全局变量,其内容正常进行腌制。 __reduce__() 返回的字符串应该是对象相对于其模块的本地名称; pickle 模块搜索模块命名空间以确定对象的模块。
返回元组时,它的长度必须介于 2 到 5 个元素之间。 可以省略可选元素,也可以提供
None
作为它们的值。 该元组的内容正常进行酸洗,并用于在取消酸洗时重建对象。 每个元素的语义是:一个可调用对象,将被调用以创建对象的初始版本。 元组的下一个元素将为这个可调用对象提供参数,后面的元素提供额外的状态信息,这些信息随后将用于完全重建腌制数据。
在 unpickling 环境中,这个对象必须是一个类,一个注册为“安全构造函数”的可调用对象(见下文),或者它必须具有一个具有真值的属性
__safe_for_unpickling__
。 否则,将在 unpickling 环境中引发UnpicklingError
。 请注意,像往常一样,可调用对象本身是按名称腌制的。可调用对象的参数元组。
2.5 版本变更: 以前,这个参数也可以是
None
。可选地,对象的状态,它将被传递给对象的 __setstate__() 方法,如 酸洗和取消普通类实例 部分所述。 如果对象没有 __setstate__() 方法,那么,如上所述,该值必须是一个字典,它将被添加到对象的 __dict__ 中。
可选地,一个迭代器(而不是一个序列)产生连续的列表项。 这些列表项将被腌制,并使用
obj.append(item)
或obj.extend(list_of_items)
附加到对象。 这主要用于列表子类,但也可以被其他类使用,只要它们具有带有适当签名的append()
和extend()
方法。 (使用append()
还是extend()
取决于使用的 pickle 协议版本以及要追加的项数,因此两者都必须支持。)可选地,一个迭代器(不是一个序列)产生连续的字典项,它应该是
(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__6(X) ]。
在要腌制的对象上实现 __reduce__()
方法的另一种方法是使用 copy_reg 模块注册可调用对象。 该模块为程序提供了一种为用户定义的类型注册“归约函数”和构造函数的方法。 归约函数具有与上述 __reduce__()
方法相同的语义和接口,不同之处在于它们使用单个参数调用,即要腌制的对象。
注册的构造函数被认为是一个“安全构造函数”,用于如上所述的 unpickling。
11.1.5.3. 酸洗和解酸外部物体
为了对象持久性的好处,pickle 模块支持对pickle 数据流之外的对象的引用的概念。 此类对象由“持久 ID”引用,它只是可打印 ASCII 字符的任意字符串。 此类名称的解析不是由 pickle 模块定义的; 它将将此分辨率委托给pickler 和unpickler 上的用户定义函数。 7
定义外部持久化id解析,需要设置pickler对象的persistent_id
属性和unpickler对象的persistent_load
属性。
要pickle具有外部持久id的对象,pickler必须有一个自定义的persistent_id()
方法,该方法接受一个对象作为参数并返回None
或该对象的持久id。 当返回 None
时,pickler 只是像平常一样腌制对象。 当返回持久 id 字符串时,pickler 将pickle 该字符串以及一个标记,以便 unpickler 将字符串识别为持久 id。
要解开外部对象,解开器必须有一个自定义的 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
设置为列表通常与 Unpickler 上的 noload()
方法结合使用。
11.1.6. 子类化解压器
默认情况下,unpickling 将导入它在 pickle 数据中找到的任何类。 您可以通过自定义 unpickler 来准确控制什么被 unpickled 和什么被调用。 不幸的是,具体如何执行取决于您使用的是 pickle 还是 cPickle。 9
在 pickle 模块中,需要从 Unpickler
派生一个子类,覆盖 load_global()
方法。 load_global()
应该从pickle 数据流中读取两行,其中第一行是包含类的模块的名称,第二行是实例类的名称。 然后它查找类,可能会导入模块并挖掘出属性,然后将它找到的内容附加到 unpickler 的堆栈中。 稍后,该类将被分配给空类的 __class__
属性,作为一种无需调用其类的 __init__()
即可神奇地创建实例的方法。 您的工作(如果您选择接受它)将是将 load_global()
压入 unpickler 的堆栈,这是您认为安全的任何类的已知安全版本。 由您来创建这样的类。 或者,如果您想禁止对实例进行所有解压,则可能会引发错误。 如果这听起来像一个黑客,你是对的。 请参阅源代码以使其工作。
使用 cPickle 使事情更干净一些,但不是很多。 要控制什么被 unpickled,您可以将 unpickler 的 find_global
属性设置为函数或 None
。 如果它是 None
,那么任何解开实例的尝试都会引发一个 UnpicklingError
。 如果是函数,那么它应该接受一个模块名和一个类名,并返回相应的类对象。 它负责查找类并执行任何必要的导入,它可能会引发错误以防止类的实例被取消。
这个故事的寓意是你应该非常小心你的应用程序解开的字符串的来源。
11.1.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()
以下示例读取生成的腌制数据。 在读取包含 pickle 的文件时,您应该以二进制模式打开文件,因为您无法确定是否使用了 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
实例被腌制,则所有属性 除 文件对象成员被保存。 当实例被 unpickle 时,文件被重新打开,并从最后一个位置继续读取。 __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."""'
11.2. 泡菜 — 更快泡菜
cPickle 模块支持 Python 对象的序列化和反序列化,提供与 pickle 模块几乎相同的接口和功能。 有几个区别,最重要的是性能和子分类性。
首先,cPickle 可以比 pickle 快 1000 倍,因为前者是用 C 实现的。 其次,在 cPickle 模块中,可调用对象 Pickler()
和 Unpickler()
是函数,而不是类。 这意味着您不能使用它们来派生自定义酸洗和取消酸洗子类。 大多数应用程序不需要此功能,应该会受益于 cPickle 模块大大改进的性能。
pickle 和 cPickle 生成的 pickle 数据流是相同的,因此可以将 pickle 和 cPickle 与现有的 pickle 互换使用。 10
cPickle 和 pickle 之间的 API 还存在其他细微差别,但是对于大多数应用程序,它们是可以互换的。 pickle 模块文档中提供了更多文档,其中包括已记录差异的列表。
脚注
- 1
- 不要将其与 marshal 模块混淆
- 2
- 在 pickle 模块中,这些可调用对象是类,您可以将其子类化以自定义行为。 然而,在 cPickle 模块中,这些可调用对象是工厂函数,因此不能被子类化。 子类化的一个常见原因是控制哪些对象实际上可以被 unpickled。 有关更多详细信息,请参阅 子类化 Unpicklers 部分。
- 3
- 警告:这是为了在不干预修改对象或其部分的情况下酸洗多个对象。 如果您修改一个对象,然后使用相同的
Pickler
实例再次对其进行腌制,则该对象不会再次被腌制 — 对其的引用被腌制并且Unpickler
将返回旧值,而不是修改了一个。 这里有两个问题:(1)检测变化,和(2)编组最小的一组变化。 垃圾收集在这里也可能成为一个问题。 - 4
- 引发的异常可能是
ImportError
或AttributeError
,但也可能是其他东西。 - 5
- 这些方法也可用于实现复制类实例。
- 6
- copy 模块中定义的浅拷贝和深拷贝操作也使用该协议。
- 7
- 对于 pickle 和 cPickle,关联这些用户定义函数的实际机制略有不同。 此处给出的描述对两种实现的工作方式相同。 pickle 模块的用户也可以使用子类来实现相同的结果,覆盖派生类中的
persistent_id()
和persistent_load()
方法。 - 8
- 我们会给你留下 Guido 和 Jim 坐在客厅里嗅泡菜的画面。
- 9
- 提醒一句:这里描述的机制使用内部属性和方法,在 Python 的未来版本中可能会发生变化。 我们打算有一天提供一个通用接口来控制这种行为,它可以在 pickle 或 cPickle 中工作。
- 10
- 由于pickle 数据格式实际上是一种面向堆栈的微小编程语言,并且在某些对象的编码方面具有一定的自由度,因此两个模块可能会为相同的输入对象生成不同的数据流。 但是,可以保证它们始终能够读取彼此的数据流。