12.1. pickle — Python 对象序列化 — Python 文档
12.1. 泡菜 — Python 对象序列化
pickle 模块实现了用于序列化和反序列化 Python 对象结构的二进制协议。 “Pickling” 是将 Python 对象层次结构转换为字节流的过程,而 “unpickling” 是逆操作,由此将字节流(来自 二进制file 或 bytes-like object) 被转换回对象层次结构。 酸洗(和取消酸洗)也称为“序列化”、“编组”、1 或“扁平化”; 然而,为了避免混淆,这里使用的术语是“酸洗”和“解酸”。
12.1.1. 与其他 Python 模块的关系
12.1.1.1. 与marshal
Python 有一个更原始的序列化模块,称为 marshal,但总的来说,pickle 应该始终是序列化 Python 对象的首选方式。 marshal 的存在主要是为了支持 Python 的 .pyc
文件。
pickle 模块跟踪它已经序列化的对象,以便以后对同一对象的引用不会再次序列化。 marshal 不这样做。
这对递归对象和对象共享都有影响。 递归对象是包含对自身的引用的对象。 这些不是由 marshal 处理的,实际上,尝试 marshal 递归对象会使您的 Python 解释器崩溃。 当在被序列化的对象层次结构中的不同位置对同一对象有多个引用时,就会发生对象共享。 pickle 仅存储此类对象一次,并确保所有其他引用都指向主副本。 共享对象保持共享状态,这对于可变对象非常重要。
marshal 不能用于序列化用户定义的类及其实例。 pickle 可以透明地保存和恢复类实例,但是类定义必须是可导入的,并且与存储对象时存在于同一模块中。
marshal 序列化格式不能保证跨 Python 版本可移植。 因为 Python 的主要工作是支持
.pyc
文件,所以 Python 实现者保留在需要时以非向后兼容的方式更改序列化格式的权利。 pickle 序列化格式保证向后兼容跨 Python 版本。
12.1.1.2. 与json
pickle 协议和 JSON(JavaScript Object Notation) 之间存在根本区别:
- JSON 是一种文本序列化格式(它输出 unicode 文本,尽管大多数时候它会被编码为
utf-8
),而 pickle 是一种二进制序列化格式; - JSON 是人类可读的,而 pickle 不是;
- JSON 是可互操作的,并且在 Python 生态系统之外广泛使用,而 pickle 是特定于 Python 的;
- 默认情况下,JSON 只能表示 Python 内置类型的一个子集,而没有自定义类; pickle 可以表示非常多的 Python 类型(其中许多是自动的,通过巧妙地使用 Python 的自省设施;复杂的情况可以通过实现 特定对象 API 来解决)。
12.1.2. 数据流格式
pickle 使用的数据格式是 Python 特定的。 这样做的好处是不受外部标准的限制,例如 JSON 或 XDR(不能代表指针共享); 然而,这意味着非 Python 程序可能无法重建腌制的 Python 对象。
默认情况下,pickle 数据格式使用相对紧凑的二进制表示。 如果您需要最佳尺寸特征,您可以有效地 压缩 腌制数据。
模块 pickletools 包含用于分析由 pickle 生成的数据流的工具。 pickletools 源代码对pickle 协议使用的操作码有大量注释。
目前有 5 种不同的协议可用于酸洗。 使用的协议越高,读取生成的泡菜所需的 Python 版本就越新。
- 协议版本 0 是原始的“人类可读”协议,向后兼容早期版本的 Python。
- 协议版本 1 是一种旧的二进制格式,它也与早期版本的 Python 兼容。
- 协议版本 2 是在 Python 2.3 中引入的。 它为 新式类 提供了更有效的酸洗。 有关协议 2 带来的改进的信息,请参阅 PEP 307。
- Python 3.0 中添加了协议版本 3。 它明确支持 bytes 对象,并且不能被 Python 2.x 解压。 这是默认协议,当需要兼容其他 Python 3 版本时推荐使用的协议。
- Python 3.4 中添加了协议版本 4。 它增加了对非常大的对象的支持,酸洗更多种类的对象,以及一些数据格式优化。 有关协议 4 带来的改进的信息,请参阅 PEP 3154。
12.1.3. 模块接口
要序列化对象层次结构,只需调用 dumps() 函数。 类似地,要反序列化数据流,您可以调用 loads() 函数。 但是,如果您想对序列化和反序列化进行更多控制,则可以分别创建 Pickler 或 Unpickler 对象。
pickle 模块提供以下常量:
- pickle.DEFAULT_PROTOCOL
- 一个整数,默认 协议版本 用于酸洗。 可能小于 HIGHEST_PROTOCOL。 目前默认的协议是 3,这是一个为 Python 3 设计的新协议。
pickle模块提供了以下功能,让pickle过程更加方便:
- pickle.dump(obj, file, protocol=None, \*, fix_imports=True)
将 obj 的腌制表示写入打开的 文件对象 文件 。 这相当于
Pickler(file, protocol).dump(obj)
。可选的 protocol 参数,一个整数,告诉pickler 使用给定的协议; 支持的协议是 0 到 HIGHEST_PROTOCOL。 如果未指定,则默认值为 DEFAULT_PROTOCOL。 如果指定了负数,则选择 HIGHEST_PROTOCOL。
file 参数必须具有接受单个字节参数的 write() 方法。 因此,它可以是为二进制写入而打开的磁盘文件、io.BytesIO 实例或任何其他符合此接口的自定义对象。
如果 fix_imports 为 true 且 protocol 小于 3,pickle 将尝试将新的 Python 3 名称映射到 Python 2 中使用的旧模块名称,以便pickle 数据流用 Python 2 可读。
- pickle.dumps(obj, protocol=None, \*, fix_imports=True)
将对象的腌制表示作为 bytes 对象返回,而不是将其写入文件。
参数 protocol 和 fix_imports 与 dump() 具有相同的含义。
- pickle.load(file, \*, fix_imports=True, encoding="ASCII", errors="strict")
从打开的 文件对象 文件 读取腌制对象表示,并返回其中指定的重组对象层次结构。 这相当于
Unpickler(file).load()
。pickle 的协议版本是自动检测的,因此不需要协议参数。 超过腌制对象表示的字节将被忽略。
参数 file 必须有两个方法,一个采用整数参数的 read() 方法和一个不需要参数的 readline() 方法。 这两种方法都应该返回字节。 因此,file 可以是为二进制读取而打开的磁盘文件、io.BytesIO 对象或任何其他符合此接口的自定义对象。
可选的关键字参数是 fix_imports、encoding 和 errors,用于控制对 Python 2 生成的 pickle 流的兼容性支持。 如果 fix_imports 为真,pickle 将尝试将旧的 Python 2 名称映射到 Python 3 中使用的新名称。 encoding 和 errors 告诉 pickle 如何解码 Python 2 腌制的 8 位字符串实例; 这些分别默认为 'ASCII' 和 'strict'。 encoding 可以是 'bytes' 来读取这些 8 位字符串实例作为字节对象。 需要使用
encoding='latin1'
来取消 NumPy 数组和 datetime、date 和 time 由 Python 2 酸洗的实例。
- pickle.loads(bytes_object, \*, fix_imports=True, encoding="ASCII", errors="strict")
从 bytes 对象读取腌制对象层次结构,并返回其中指定的重构对象层次结构。
pickle 的协议版本是自动检测的,因此不需要协议参数。 超过腌制对象表示的字节将被忽略。
可选的关键字参数是 fix_imports、encoding 和 errors,用于控制对 Python 2 生成的 pickle 流的兼容性支持。 如果 fix_imports 为真,pickle 将尝试将旧的 Python 2 名称映射到 Python 3 中使用的新名称。 encoding 和 errors 告诉 pickle 如何解码 Python 2 腌制的 8 位字符串实例; 这些分别默认为 'ASCII' 和 'strict'。 encoding 可以是 'bytes' 来读取这些 8 位字符串实例作为字节对象。 需要使用
encoding='latin1'
来取消 NumPy 数组和 datetime、date 和 time 由 Python 2 酸洗的实例。
pickle 模块定义了三个异常:
- exception pickle.PickleError
- 其他酸洗异常的公共基类。 它继承了 异常 。
- exception pickle.PicklingError
Pickler 遇到不可拾取的对象时引发错误。 它继承了 PickleError。
请参阅可以腌制和不腌制的物品?了解可以腌制的物品种类。
- exception pickle.UnpicklingError
当解压对象出现问题时引发错误,例如数据损坏或安全违规。 它继承了 PickleError。
请注意,在 unpickling 期间也可能引发其他异常,包括(但不一定限于)AttributeError、EOFError、ImportError 和 IndexError。
pickle 模块导出两个类,Pickler 和 Unpickler:
- class pickle.Pickler(file, protocol=None, \*, fix_imports=True)
这需要一个二进制文件来编写pickle数据流。
可选的 protocol 参数,一个整数,告诉pickler 使用给定的协议; 支持的协议是 0 到 HIGHEST_PROTOCOL。 如果未指定,则默认值为 DEFAULT_PROTOCOL。 如果指定了负数,则选择 HIGHEST_PROTOCOL。
file 参数必须具有接受单个字节参数的 write() 方法。 因此,它可以是为二进制写入而打开的磁盘文件、io.BytesIO 实例或任何其他符合此接口的自定义对象。
如果 fix_imports 为 true 且 protocol 小于 3,pickle 将尝试将新的 Python 3 名称映射到 Python 2 中使用的旧模块名称,以便pickle 数据流用 Python 2 可读。
- dump(obj)
将 obj 的腌制表示写入构造函数中给出的打开文件对象。
- persistent_id(obj)
默认什么都不做。 这存在以便子类可以覆盖它。
如果 persistent_id() 返回
None
,obj 像往常一样被腌制。 任何其他值都会导致 Pickler 将返回值作为 obj 的持久 ID 发出。 这个持久ID的含义应该由Unpickler.persistent_load()定义。 请注意, persistent_id() 返回的值本身不能具有持久 ID。有关详细信息和使用示例,请参阅 外部对象持久性 。
- dispatch_table
Pickler 对象的调度表是 归约函数 的注册表,可以使用 copyreg.pickle() 声明。 它是一个映射,其键是类,值是归约函数。 归约函数采用关联类的单个参数,并且应符合与
__reduce__()
方法相同的接口。默认情况下,pickler 对象将没有 dispatch_table 属性,而是使用由 copyreg 模块管理的全局调度表。 但是,要为特定的pickler 对象自定义酸洗,可以将 dispatch_table 属性设置为类似 dict 的对象。 或者,如果 Pickler 的子类具有 dispatch_table 属性,那么这将用作该类实例的默认调度表。
有关用法示例,请参阅 调度表 。
3.3 版中的新功能。
- fast
已弃用。 如果设置为真值,则启用快速模式。 快速模式禁用备忘录的使用,因此通过不生成多余的 PUT 操作码来加速酸洗过程。 它不应该与自引用对象一起使用,否则会导致 Picker 无限递归。
如果您需要更紧凑的泡菜,请使用 pickletools.optimize()。
- class pickle.Unpickler(file, \*, fix_imports=True, encoding="ASCII", errors="strict")
这需要一个二进制文件来读取泡菜数据流。
pickle 的协议版本是自动检测的,因此不需要协议参数。
参数 file 必须有两个方法,一个采用整数参数的 read() 方法和一个不需要参数的 readline() 方法。 这两种方法都应该返回字节。 因此,file 可以是为二进制读取而打开的磁盘文件对象、io.BytesIO 对象或任何其他符合此接口的自定义对象。
可选的关键字参数是 fix_imports、encoding 和 errors,用于控制对 Python 2 生成的 pickle 流的兼容性支持。 如果 fix_imports 为真,pickle 将尝试将旧的 Python 2 名称映射到 Python 3 中使用的新名称。 encoding 和 errors 告诉 pickle 如何解码 Python 2 腌制的 8 位字符串实例; 这些分别默认为 'ASCII' 和 'strict'。 encoding 可以是 'bytes' 来读取这些 8 位字符串实例作为字节对象。
- load()
从构造函数中给出的打开文件对象中读取腌制对象表示,并返回其中指定的重构对象层次结构。 超过腌制对象表示的字节将被忽略。
- persistent_load(pid)
默认情况下引发 UnpicklingError。
如果已定义,persistent_load() 应返回由持久 ID pid 指定的对象。 如果遇到无效的持久 ID,则应引发 UnpicklingError。
有关详细信息和使用示例,请参阅 外部对象持久性 。
- find_class(module, name)
如有必要,导入 module 并从中返回名为 name 的对象,其中 module 和 name 参数为 str ] 对象。 请注意,与其名称所暗示的不同,find_class() 也用于查找函数。
子类可以覆盖它以控制对象的类型和加载方式,从而潜在地降低安全风险。 有关详细信息,请参阅 限制全局变量 。
12.1.4. 什么可以腌制和不腌制?
可以腌制以下类型:
None
、True
和False
- 整数、浮点数、复数
- 字符串、字节、字节数组
- 仅包含可picklable 对象的元组、列表、集合和字典
- 在模块顶层定义的函数(使用 def,而不是 lambda)
- 定义在模块顶层的内置函数
- 在模块顶层定义的类
- 其 __dict__ 或调用
__getstate__()
的结果是可酸洗的类的实例(有关详细信息,请参阅 酸洗类实例 部分)。
尝试腌制不可腌制的对象将引发 PicklingError 异常; 发生这种情况时,可能已经将未指定数量的字节写入底层文件。 尝试腌制高度递归的数据结构可能会超过最大递归深度,在这种情况下将引发 RecursionError。 您可以使用 sys.setrecursionlimit() 小心地提高此限制。
请注意,函数(内置和用户定义的)由“完全限定”名称引用而不是值来腌制。 2 这意味着只腌制函数名称,以及定义函数的模块名称。 函数的代码及其任何函数属性都不会被腌制。 因此定义模块必须在 unpickling 环境中可导入,并且模块必须包含命名对象,否则将引发异常。 3
类似地,类通过命名引用进行酸洗,因此在 unpickling 环境中也适用相同的限制。 请注意,类的代码或数据均未经过酸洗,因此在以下示例中,类属性 attr
在 unpickling 环境中不会恢复:
class Foo:
attr = 'A class attribute'
picklestring = pickle.dumps(Foo)
这些限制就是为什么必须在模块的顶层定义可腌制的函数和类。
类似地,当类实例被腌制时,它们的类的代码和数据不会与它们一起被腌制。 只有实例数据被腌制。 这是故意完成的,因此您可以修复类中的错误或向类添加方法,并且仍然加载使用早期版本的类创建的对象。 如果您计划拥有将看到一个类的多个版本的长期对象,那么在对象中放置一个版本号可能是值得的,以便可以通过类的 __setstate__()
方法进行适当的转换。
12.1.5. 酸洗类实例
在本节中,我们将描述可用于定义、自定义和控制类实例的pickle 和unpickle 方式的一般机制。
在大多数情况下,不需要额外的代码来使实例可以pickle。 默认情况下,pickle 将通过自省检索实例的类和属性。 当一个类实例被 unpickled 时,它的 __init__()
方法通常是 not 调用。 默认行为首先创建一个未初始化的实例,然后恢复保存的属性。 以下代码显示了此行为的实现:
def save(obj):
return (obj.__class__, obj.__dict__)
def load(cls, attributes):
obj = cls.__new__(cls)
obj.__dict__.update(attributes)
return obj
类可以通过提供一种或几种特殊方法来改变默认行为:
- object.__getnewargs_ex__()
在协议 2 和更新版本中,实现 __getnewargs_ex__() 方法的类可以在 unpickling 时指定传递给 __new__() 方法的值。 该方法必须返回一对
(args, kwargs)
,其中 args 是位置参数的元组,而 kwargs 是用于构造对象的命名参数字典。 这些将在 unpickling 时传递给 __new__() 方法。如果您的类的 __new__() 方法需要仅关键字参数,则您应该实现此方法。 否则,为了兼容性,建议实现 __getnewargs__()。
3.6 版更改: __getnewargs_ex__() 现在用于协议 2 和 3。
- object.__getnewargs__()
此方法的用途与 __getnewargs_ex__() 类似,但仅支持位置参数。 它必须返回一个参数元组
args
,该元组将在解压时传递给 __new__() 方法。如果定义了 __getnewargs_ex__(),则不会调用 __getnewargs__()。
3.6 版更改: 在 Python 3.6 之前, __getnewargs__() 在协议 2 和 3 中被调用而不是 __getnewargs_ex__()。
- object.__getstate__()
- 类可以进一步影响它们的实例如何被腌制; 如果类定义了 __getstate__() 方法,则调用该方法并将返回的对象作为实例的内容进行腌制,而不是实例字典的内容。 如果 __getstate__() 方法不存在,则实例的 __dict__ 像往常一样被腌制。
- object.__setstate__(state)
在 unpickling 时,如果该类定义了 __setstate__(),则以 unpickled 状态调用它。 在这种情况下,状态对象不需要是字典。 否则,pickled 状态必须是一个字典,并且它的项被分配给新实例的字典。
笔记
如果 __getstate__() 返回一个假值,__setstate__() 方法在 unpickling 时不会被调用。
有关如何使用方法 __getstate__()
和 __setstate__()
的更多信息,请参阅 处理有状态对象 部分。
笔记
在 unpickling 时,可能会在实例上调用一些方法,如 __getattr__()
、__getattribute__()
或 __setattr__()
。 如果这些方法依赖于某些内部不变量为真,则类型应实现 __getnewargs__()
或 __getnewargs_ex__()
来建立这样的不变量; 否则,不会调用 __new__()
和 __init__()
。
正如我们将看到的,pickle 不直接使用上述方法。 事实上,这些方法是实现 __reduce__()
特殊方法的复制协议的一部分。 复制协议提供了一个统一的接口,用于检索酸洗和复制对象所需的数据。 4
尽管功能强大,但直接在类中实现 __reduce__()
很容易出错。 因此,类设计者应尽可能使用高级接口(即 __getnewargs_ex__()
、__getstate__()
和 __setstate__()
)。 然而,我们将展示使用 __reduce__()
是唯一选择或导致更有效酸洗或两者兼而有之的情况。
- object.__reduce__()
该接口目前定义如下。 __reduce__() 方法不接受任何参数,并且应该返回一个字符串或者最好是一个元组(返回的对象通常被称为“reduce 值”)。
如果返回字符串,则该字符串应被解释为全局变量的名称。 它应该是对象相对于其模块的本地名称; pickle 模块搜索模块命名空间以确定对象的模块。 此行为通常对单身人士有用。
返回元组时,它的长度必须介于 2 到 5 个项目之间。 可以省略可选项目,也可以提供
None
作为它们的值。 每个项目的语义按顺序排列:一个可调用对象,将被调用以创建对象的初始版本。
可调用对象的参数元组。 如果可调用对象不接受任何参数,则必须给出一个空元组。
可选地,对象的状态,将传递给对象的 __setstate__() 方法,如前所述。 如果对象没有这样的方法,那么该值必须是一个字典,它将被添加到对象的 __dict__ 属性中。
可选地,一个迭代器(而不是一个序列)产生连续的项目。 这些项目将使用
obj.append(item)
或批量使用obj.extend(list_of_items)
附加到对象。 这主要用于列表子类,但也可以被其他类使用,只要它们具有带有适当签名的append()
和extend()
方法。 (使用append()
还是extend()
取决于使用的 pickle 协议版本以及要追加的项数,因此两者都必须支持。)可选地,迭代器(不是序列)产生连续的键值对。 这些项目将使用
obj[key] = value
存储到对象中。 这主要用于字典子类,但也可以被其他类使用,只要它们实现 __setitem__()。
- object.__reduce_ex__(protocol)
- 或者,可以定义 __reduce_ex__() 方法。 唯一的区别是此方法应采用单个整数参数,即协议版本。 定义后,pickle 会比 __reduce__() 方法更喜欢它。 此外,__reduce__() 自动成为扩展版本的同义词。 此方法的主要用途是为较旧的 Python 版本提供向后兼容的 reduce 值。
12.1.5.1. 外部对象的持久性
为了对象持久性的好处,pickle 模块支持对pickle 数据流之外的对象的引用的概念。 此类对象由持久 ID 引用,该 ID 应该是一串字母数字字符(对于协议 0)5 或只是一个任意对象(对于任何更新的协议)。
pickle 模块未定义此类持久 ID 的解析; 它将将此分辨率委托给pickler 和unpickler 上的用户定义方法,分别为persistent_id() 和persistent_load()。
要pickle具有外部持久id的对象,pickler必须有一个自定义的persistent_id()方法,该方法接受一个对象作为参数并返回None
或该对象的持久id。 当返回 None
时,pickler 只是像平常一样腌制对象。 当返回一个持久 ID 字符串时,pickler 将pickle 该对象以及一个标记,以便 unpickler 将其识别为持久 ID。
要解开外部对象,解开器必须有一个自定义的 persistent_load() 方法,该方法接受一个持久 ID 对象并返回引用的对象。
这是一个综合示例,展示了如何使用持久 ID 通过引用来pickle外部对象。
# Simple example presenting how persistent ID can be used to pickle
# external objects by reference.
import pickle
import sqlite3
from collections import namedtuple
# Simple class representing a record in our database.
MemoRecord = namedtuple("MemoRecord", "key, task")
class DBPickler(pickle.Pickler):
def persistent_id(self, obj):
# Instead of pickling MemoRecord as a regular class instance, we emit a
# persistent ID.
if isinstance(obj, MemoRecord):
# Here, our persistent ID is simply a tuple, containing a tag and a
# key, which refers to a specific record in the database.
return ("MemoRecord", obj.key)
else:
# If obj does not have a persistent ID, return None. This means obj
# needs to be pickled as usual.
return None
class DBUnpickler(pickle.Unpickler):
def __init__(self, file, connection):
super().__init__(file)
self.connection = connection
def persistent_load(self, pid):
# This method is invoked whenever a persistent ID is encountered.
# Here, pid is the tuple returned by DBPickler.
cursor = self.connection.cursor()
type_tag, key_id = pid
if type_tag == "MemoRecord":
# Fetch the referenced record from the database and return it.
cursor.execute("SELECT * FROM memos WHERE key=?", (str(key_id),))
key, task = cursor.fetchone()
return MemoRecord(key, task)
else:
# Always raises an error if you cannot return the correct object.
# Otherwise, the unpickler will think None is the object referenced
# by the persistent ID.
raise pickle.UnpicklingError("unsupported persistent object")
def main():
import io
import pprint
# Initialize and populate our database.
conn = sqlite3.connect(":memory:")
cursor = conn.cursor()
cursor.execute("CREATE TABLE memos(key INTEGER PRIMARY KEY, task TEXT)")
tasks = (
'give food to fish',
'prepare group meeting',
'fight with a zebra',
)
for task in tasks:
cursor.execute("INSERT INTO memos VALUES(NULL, ?)", (task,))
# Fetch the records to be pickled.
cursor.execute("SELECT * FROM memos")
memos = [MemoRecord(key, task) for key, task in cursor]
# Save the records using our custom DBPickler.
file = io.BytesIO()
DBPickler(file).dump(memos)
print("Pickled records:")
pprint.pprint(memos)
# Update a record, just for good measure.
cursor.execute("UPDATE memos SET task='learn italian' WHERE key=1")
# Load the records from the pickle data stream.
file.seek(0)
memos = DBUnpickler(file, conn).load()
print("Unpickled records:")
pprint.pprint(memos)
if __name__ == '__main__':
main()
12.1.5.2. 调度表
如果您想自定义某些类的酸洗而不干扰任何其他依赖酸洗的代码,那么您可以创建一个带有私有调度表的酸洗器。
由 copyreg 模块管理的全局调度表可作为 copyreg.dispatch_table
使用。 因此,可以选择使用 copyreg.dispatch_table
的修改副本作为私有调度表。
例如
f = io.BytesIO()
p = pickle.Pickler(f)
p.dispatch_table = copyreg.dispatch_table.copy()
p.dispatch_table[SomeClass] = reduce_SomeClass
创建一个 pickle.Pickler 的实例,带有一个专门处理 SomeClass
类的私有调度表。 或者,代码
class MyPickler(pickle.Pickler):
dispatch_table = copyreg.dispatch_table.copy()
dispatch_table[SomeClass] = reduce_SomeClass
f = io.BytesIO()
p = MyPickler(f)
执行相同的操作,但 MyPickler
的所有实例将默认共享相同的调度表。 使用 copyreg 模块的等效代码是
copyreg.pickle(SomeClass, reduce_SomeClass)
f = io.BytesIO()
p = pickle.Pickler(f)
12.1.5.3. 处理有状态对象
这是一个示例,展示了如何修改类的酸洗行为。 TextReader
类打开一个文本文件,并在每次调用其 readline()
方法时返回行号和行内容。 如果一个 TextReader
实例被腌制,则所有属性 除 文件对象成员被保存。 当实例被 unpickle 时,文件被重新打开,并从最后一个位置继续读取。 __setstate__()
和 __getstate__()
方法用于实现此行为。
class TextReader:
"""Print and number lines in a text file."""
def __init__(self, filename):
self.filename = filename
self.file = open(filename)
self.lineno = 0
def readline(self):
self.lineno += 1
line = self.file.readline()
if not line:
return None
if line.endswith('\n'):
line = line[:-1]
return "%i: %s" % (self.lineno, line)
def __getstate__(self):
# Copy the object's state from self.__dict__ which contains
# all our instance attributes. Always use the dict.copy()
# method to avoid modifying the original state.
state = self.__dict__.copy()
# Remove the unpicklable entries.
del state['file']
return state
def __setstate__(self, state):
# Restore instance attributes (i.e., filename and lineno).
self.__dict__.update(state)
# Restore the previously opened file's state. To do so, we need to
# reopen it and read from it until the line count is restored.
file = open(self.filename)
for _ in range(self.lineno):
file.readline()
# Finally, save the file.
self.file = file
示例用法可能是这样的:
>>> reader = TextReader("hello.txt")
>>> reader.readline()
'1: Hello world!'
>>> reader.readline()
'2: I am line number two.'
>>> new_reader = pickle.loads(pickle.dumps(reader))
>>> new_reader.readline()
'3: Goodbye!'
12.1.6. 限制全局变量
默认情况下,unpickling 将导入它在 pickle 数据中找到的任何类或函数。 对于许多应用程序,这种行为是不可接受的,因为它允许 unpickler 导入和调用任意代码。 只需考虑加载时这个手工制作的pickle数据流的作用:
>>> import pickle
>>> pickle.loads(b"cos\nsystem\n(S'echo hello world'\ntR.")
hello world
0
在这个例子中,unpickler 导入 os.system() 函数,然后应用字符串参数“echo hello world”。 尽管此示例无害,但不难想象可能会损坏您的系统的示例。
出于这个原因,您可能希望通过自定义 Unpickler.find_class() 来控制取消腌制的内容。 与其名称所暗示的不同,只要请求全局(即类或函数),就会调用 Unpickler.find_class()。 因此,可以完全禁止全局变量或将它们限制为安全子集。
下面是一个 unpickler 的例子,它只允许加载 builtins 模块中的几个安全类:
import builtins
import io
import pickle
safe_builtins = {
'range',
'complex',
'set',
'frozenset',
'slice',
}
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
# Only allow safe classes from builtins.
if module == "builtins" and name in safe_builtins:
return getattr(builtins, name)
# Forbid everything else.
raise pickle.UnpicklingError("global '%s.%s' is forbidden" %
(module, name))
def restricted_loads(s):
"""Helper function analogous to pickle.loads()."""
return RestrictedUnpickler(io.BytesIO(s)).load()
我们的 unpickler 工作的示例用法旨在:
>>> restricted_loads(pickle.dumps([1, 2, range(15)]))
[1, 2, range(0, 15)]
>>> restricted_loads(b"cos\nsystem\n(S'echo hello world'\ntR.")
Traceback (most recent call last):
...
pickle.UnpicklingError: global 'os.system' is forbidden
>>> restricted_loads(b'cbuiltins\neval\n'
... b'(S\'getattr(__import__("os"), "system")'
... b'("echo hello world")\'\ntR.')
Traceback (most recent call last):
...
pickle.UnpicklingError: global 'builtins.eval' is forbidden
正如我们的例子所示,你必须小心你允许去腌制的东西。 因此,如果担心安全性,您可能需要考虑替代方案,例如 xmlrpc.client 中的编组 API 或第三方解决方案。
12.1.8. 例子
对于最简单的代码,使用 dump() 和 load() 函数。
import pickle
# An arbitrary collection of objects supported by pickle.
data = {
'a': [1, 2.0, 3, 4+6j],
'b': ("character string", b"byte string"),
'c': {None, True, False}
}
with open('data.pickle', 'wb') as f:
# Pickle the 'data' dictionary using the highest protocol available.
pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)
以下示例读取生成的腌制数据。
import pickle
with open('data.pickle', 'rb') as f:
# The protocol version used is detected automatically, so we do not
# have to specify it.
data = pickle.load(f)
也可以看看
- 模块 copyreg
- 扩展类型的 Pickle 接口构造函数注册。
- 模块 pickletools
- 用于处理和分析腌制数据的工具。
- 模块 搁架
- 对象的索引数据库; 使用 pickle。
- 模块复制
- 浅层和深层对象复制。
- 模块元帅
- 内置类型的高性能序列化。
脚注
- 1
- 不要将其与 marshal 模块混淆
- 2
- 这就是为什么 lambda 函数不能被腌制的原因:所有 lambda 函数共享相同的名称:
<lambda>
。 - 3
- 引发的异常可能是 ImportError 或 AttributeError,但也可能是别的东西。
- 4
- copy 模块使用该协议进行浅拷贝和深拷贝操作。
- 5
- 对字母数字字符的限制是因为协议 0 中的持久 ID 由换行符分隔。 因此,如果持久 ID 中出现任何类型的换行符,则生成的 pickle 将变得不可读。