pickle ---python对象序列化

源代码: Lib/pickle.py


这个 pickle 模块实现用于序列化和反序列化Python对象结构的二进制协议。 “酸洗” 是将Python对象层次结构转换为字节流的过程,并且 “去酸洗” 是反向操作,其中字节流(来自 binary filebytes-like object )转换回对象层次结构。酸洗(和解压)也被称为“序列化”、“编组”。 1 或者“压扁”;但是,为了避免混淆,这里使用的术语是“酸洗”和“不粘”。

警告

这个 pickle 模块 不安全 . 只有你信任的不敏感的数据。

可以构建恶意pickle数据 取消锁定期间执行任意代码 . 永远不要取消对可能来自不受信任源或可能被篡改的数据的锁定。

考虑使用 hmac 如果你需要确保它没有被篡改。

更安全的序列化格式,如 json 如果正在处理不受信任的数据,则可能更合适。见 比较 json .

与其他python模块的关系

比较 marshal

python有一个更原始的序列化模块,名为 marshal ,但一般来说 pickle 应该始终是序列化Python对象的首选方法。 marshal 主要支持python的 .pyc 文件夹。

这个 pickle 模块与 marshal 在几个重要方面:

  • 这个 pickle 模块跟踪已序列化的对象,以便以后对同一对象的引用不会再次序列化。 marshal 不这样做。

    这对递归对象和对象共享都有影响。递归对象是包含对自身引用的对象。这些不是由marshal处理的,事实上,尝试对递归对象进行marshal处理会使Python解释器崩溃。当在被序列化的对象层次结构中的不同位置有多个对同一对象的引用时,会发生对象共享。 pickle 只存储此类对象一次,并确保所有其他引用指向主副本。共享对象保持共享,这对于可变对象非常重要。

  • marshal 不能用于序列化用户定义的类及其实例。 pickle 可以透明地保存和还原类实例,但是类定义必须是可导入的,并且与存储对象时位于同一模块中。

  • 这个 marshal 不能保证序列化格式可以跨Python版本进行移植。因为它在生活中的主要工作是支持 .pyc 文件,python实现者保留在需要时以非向后兼容的方式更改序列化格式的权利。这个 pickle 如果选择了兼容的pickle协议,并且pickle和unpickling代码处理的是python 2到python 3类型的差异(如果您的数据跨越了惟一的中断更改语言边界),那么序列化格式保证在python版本之间向后兼容。

比较 json

pickle协议和 JSON (JavaScript Object Notation)

  • JSON是一种文本序列化格式(它输出Unicode文本,尽管大多数时候它被编码为 utf-8 )而pickle是二进制序列化格式;

  • JSON是人类可读的,而pickle则不是;

  • JSON是可互操作的,在Python生态系统之外被广泛使用,而pickle是特定于Python的;

  • 默认情况下,JSON只能表示Python内置类型的一个子集,不能表示自定义类;pickle可以表示非常多的Python类型(其中许多类型是通过巧妙地使用Python的内省功能自动实现的;复杂的情况可以通过实现 specific object APIs

  • 与pickle不同,反序列化不受信任的JSON本身不会创建任意代码执行漏洞。

参见

这个 json 模块:允许JSON序列化和反序列化的标准库模块。

数据流格式

所使用的数据格式 pickle 是特定于python的。这样做的好处是,外部标准(如JSON或XDR)没有任何限制(不能表示指针共享);但这意味着非Python程序可能无法重建pickled python对象。

默认情况下, pickle 数据格式使用相对紧凑的二进制表示。如果您需要最佳的尺寸特性,您可以有效地 compress 腌制数据

模块 pickletools 包含用于分析由生成的数据流的工具 pickle . pickletools 源代码对pickle协议使用的操作码有广泛的评论。

目前有6种不同的协议可用于酸洗。使用的协议越高,Python的版本就越需要读取生成的pickle。

  • 协议版本0是原始的“人类可读”协议,与早期版本的Python向后兼容。

  • 协议版本1是一种旧的二进制格式,它也与早期版本的python兼容。

  • python 2.3中引入了协议版本2。它提供了更有效的酸洗 new-style class ES。参照 PEP 307 有关协议2带来的改进的信息。

  • 在python 3.0中添加了协议版本3。它对 bytes 对象,不能被python 2.x解包。这是python 3.0--3.7中的默认协议。

  • 在python 3.4中添加了协议版本4。它增加了对非常大的对象的支持、对更多类型的对象的清除以及一些数据格式优化。它是从python 3.8开始的默认协议。参照 PEP 3154 有关协议4所带来的改进的信息。

  • 在Python3.8中添加了协议版本5。它增加了对带外数据的支持和对带内数据的加速。参考 PEP 574 关于协议5带来的改进的信息。

注解

序列化是比持久性更原始的概念;尽管 pickle 它不处理命名持久对象的问题,也不处理并发访问持久对象的问题(甚至更复杂)。这个 pickle 模块可以将复杂对象转换为字节流,并且可以将字节流转换为具有相同内部结构的对象。也许处理这些字节流最明显的事情是将它们写到一个文件中,但是也可以想象通过网络发送它们或者将它们存储在数据库中。这个 shelve 模块提供了一个简单的接口,用于在DBM样式的数据库文件上pickle和unpickle对象。

模块接口

要序列化对象层次结构,只需调用 dumps() 功能。同样,要反序列化数据流,可以调用 loads() 功能。但是,如果希望对序列化和反序列化进行更多控制,可以创建一个 PicklerUnpickler 对象。

这个 pickle 模块提供以下常量:

pickle.HIGHEST_PROTOCOL

一个整数,最高的 protocol version 可用。此值可以作为 协议 函数值 dump()dumps() 以及 Pickler 构造函数。

pickle.DEFAULT_PROTOCOL

一个整数,默认值 protocol version 用于酸洗。可能小于 HIGHEST_PROTOCOL . 目前默认的协议是4,首先在Python3.4中引入,并且与以前的版本不兼容。

在 3.0 版更改: 默认协议为3。

在 3.8 版更改: 默认协议为4。

这个 pickle 模块提供以下功能,使pickling过程更加方便:

pickle.dump(obj, file, protocol=None, *, fix_imports=True, buffer_callback=None)

Write the pickled representation of the object obj to the open file object file. This is equivalent to Pickler(file, protocol).dump(obj).

参数 file协议fix_importsbuffer_callback 与中的含义相同 Pickler 建造师。

在 3.8 版更改: 这个 buffer_callback 已添加参数。

pickle.dumps(obj, protocol=None, *, fix_imports=True, buffer_callback=None)

返回对象的pickled表示 obj 作为一个 bytes 对象,而不是将其写入文件。

参数 协议fix_importsbuffer_callback 与中的含义相同 Pickler 建造师。

在 3.8 版更改: 这个 buffer_callback 已添加参数。

pickle.load(file, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None)

Read the pickled representation of an object from the open file object file and return the reconstituted object hierarchy specified therein. This is equivalent to Unpickler(file).load().

pickle的协议版本是自动检测的,因此不需要协议参数。超过对象的pickle表示的字节将被忽略。

参数 filefix_imports编码错误严格的缓冲器 与中的含义相同 Unpickler 建造师。

在 3.8 版更改: 这个 缓冲器 已添加参数。

pickle.loads(data, /, *, fix_imports=True, encoding="ASCII", errors="strict", buffers=None)

返回pickled表示的重构对象层次结构 data 一个物体的。 data 必须是 bytes-like object .

pickle的协议版本是自动检测的,因此不需要协议参数。超过对象的pickle表示的字节将被忽略。

参数 filefix_imports编码错误严格的缓冲器 与中的含义相同 Unpickler 建造师。

在 3.8 版更改: 这个 缓冲器 已添加参数。

这个 pickle 模块定义了三个例外:

exception pickle.PickleError

其他酸洗异常的公共基类。它继承 Exception .

exception pickle.PicklingError

遇到不可选取的对象时引发的错误 Pickler .它继承了 PickleError .

参照 什么东西可以腌制和剥下? 了解什么样的物品可以腌制。

exception pickle.UnpicklingError

取消拾取对象时出现问题(如数据损坏或安全冲突)时引发的错误。它继承 PickleError .

请注意,在取消领料期间也可能会引发其他异常,包括(但不一定限于)attributeError、eoferror、importError和indexError。

这个 pickle 模块导出三个类, PicklerUnpicklerPickleBuffer

class pickle.Pickler(file, protocol=None, *, fix_imports=True, buffer_callback=None)

这需要一个二进制文件来写入pickle数据流。

可选的 协议 参数是一个整数,告诉pickler使用给定的协议;支持的协议为0到 HIGHEST_PROTOCOL . 如果未指定,则默认值为 DEFAULT_PROTOCOL . 如果指定负数, HIGHEST_PROTOCOL 被选中。

这个 file 参数必须具有可接受单个字节参数的write()方法。因此,它可以是一个为二进制写入而打开的磁盘上文件, io.BytesIO 实例或满足此接口的任何其他自定义对象。

如果 fix_imports 是真的 协议 小于3,pickle将尝试将新的python 3名称映射到python 2中使用的旧模块名称,以便pickle数据流可以用python 2读取。

如果 buffer_callback 为“无”(默认),缓冲区视图序列化为 file 作为泡菜流的一部分。

如果 buffer_callback 不是“无”,则可以使用缓冲区视图调用任意次数。如果回调返回一个错误值(如none),则给定的缓冲区为 out-of-band 否则,缓冲区在带内序列化,即在pickle流中。

如果 buffer_callback 不是没有 协议 不小于5。

在 3.8 版更改: 这个 buffer_callback 已添加参数。

dump(obj)

编写的pickled表示 obj 到构造函数中给定的打开文件对象。

persistent_id(obj)

默认情况下不执行任何操作。它存在,因此子类可以覆盖它。

如果 persistent_id() 返回 Noneobj 像往常一样腌制。任何其他价值原因 Pickler 将返回值作为的持久ID发出 obj . 此持久ID的含义应定义为 Unpickler.persistent_load() . 注意返回的值 persistent_id() 自身不能有持久ID。

外部对象的持久性 有关详细信息和使用示例。

dispatch_table

pickler对象的调度表是 约简函数 可以使用 copyreg.pickle() . 它是一种映射,其键是类,值是约简函数。reducation函数接受关联类的单个参数,并应与 __reduce__() 方法。

默认情况下,pickler对象将没有 dispatch_table 属性,它将使用由 copyreg 模块。但是,要为特定的pickler对象自定义pickle,可以设置 dispatch_table 属性为类似dict的对象。或者,如果 Pickler 有一个 dispatch_table 属性,则此属性将用作该类实例的默认调度表。

调度表 用于示例。

3.3 新版功能.

reducer_override(self, obj)

特殊减速器,可在 Pickler 子类。此方法优先于 dispatch_table . 它应该与 __reduce__() 方法,并且可以选择返回 NotImplemented 回退 dispatch_table -已注册的酸洗还原剂 obj .

有关详细示例,请参见 类型、函数和其他对象的自定义缩减 .

3.8 新版功能.

fast

不赞成的如果设置为真值,则启用快速模式。快速模式禁用备忘录的使用,因此通过不生成多余的Put操作码来加快酸洗过程。它不应该与自引用对象一起使用,否则会导致 Pickler 无限地循环。

使用 pickletools.optimize() 如果你需要更紧凑的泡菜。

class pickle.Unpickler(file, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None)

这需要一个二进制文件来读取pickle数据流。

自动检测pickle的协议版本,因此不需要协议参数。

参数 file 必须有三个方法,一个采用整数参数的read()方法,一个采用缓冲区参数的readinto()方法和一个不需要参数的readline()方法,如 io.BufferedIOBase 接口。因此 file 可以是为二进制读取而打开的磁盘上文件, io.BytesIO 对象,或满足此接口的任何其他自定义对象。

可选参数 fix_imports编码错误 用于控制由python 2生成的pickle流的兼容性支持。如果 fix_imports 是的,pickle将尝试将旧的python 2名称映射到python 3中使用的新名称。这个 编码错误 告诉pickle如何解码python 2 pickle处理的8位字符串实例;它们分别默认为'ascii'和'strict'。这个 编码 可以是“bytes”,将这些8位字符串实例作为bytes对象读取。使用 encoding='latin1' 需要取消拾取numpy数组和的实例 datetimedatetime 用 Python 2腌制。

如果 缓冲器 为无(默认值),则反序列化所需的所有数据都必须包含在pickle流中。这意味着 buffer_callbackPickler 被实例化(或 dump()dumps() 被称为。

如果 缓冲器 不是“无”,它应该是启用缓冲区的对象的iterable,每当pickle流引用 out-of-band 缓冲区视图。这些缓冲区是为了 buffer_callback 一个泡菜对象。

在 3.8 版更改: 这个 缓冲器 已添加参数。

load()

从构造函数中给定的打开文件对象中读取对象的pickled表示,并返回其中指定的重构对象层次结构。超过对象的pickle表示的字节将被忽略。

persistent_load(pid)

养一个 UnpicklingError 默认情况下。

如果定义, persistent_load() 应返回由持久ID指定的对象 pid . 如果遇到无效的持久ID,则 UnpicklingError 应该提高。

外部对象的持久性 有关详细信息和使用示例。

find_class(module, name)

输入 模块 如有必要,返回调用的对象 name 从它,在哪里 模块name 参数是 str 物体。注意,与它的名字不同的是, find_class() 也用于查找函数。

子类可能会覆盖这一点,以控制对象的类型和加载方式,从而潜在地降低安全风险。参照 限制全局 有关详细信息。

提出一个 auditing event pickle.find_class 带着论据 modulename .

class pickle.PickleBuffer(buffer)

表示可选取数据的缓冲区的包装。 缓冲区 必须是 buffer-providing 对象,例如 bytes-like object 或者N维数组。

PickleBuffer 本身是一个缓冲区提供程序,因此可以将其传递给其他期望缓冲区提供对象的API,例如 memoryview .

PickleBuffer 对象只能使用pickle协议5或更高版本进行序列化。他们有资格 out-of-band serialization .

3.8 新版功能.

raw()

返回A memoryview 缓冲区下的内存区域。返回的对象是一个具有格式的一维C-连续内存视图。 B (无符号字节)。 BufferError 如果缓冲区不是C-或Fortran连续的,则引发。

release()

释放picklebuffer对象公开的基础缓冲区。

什么东西可以腌制和剥下?

可以腌制以下类型:

  • None, True, and False

  • 整数、浮点数、复数

  • 字符串、字节、字节数组

  • 仅包含可选取对象的元组、列表、集和字典

  • 在模块顶层定义的函数(使用 def 不是 lambda

  • 在模块的顶层定义的内置函数

  • 在模块的顶层定义的类

  • 此类类的实例 __dict__ 或者调用的结果 __getstate__() 可拾取(参见第节 酸洗类实例 详情请参阅。

尝试pickle不可拾取的对象将引发 PicklingError 异常;发生这种情况时,可能已经向基础文件写入了未指定数量的字节。尝试pickle高度递归的数据结构可能会超过最大递归深度, RecursionError 在这种情况下将被引发。你可以用 sys.setrecursionlimit() .

请注意,函数(内置的和用户定义的)是由“完全限定的”名称引用而不是值来处理的。 2 这意味着只有函数名以及在其中定义函数的模块名被pickle。函数的代码及其任何函数属性都不会被pickle。因此,定义模块必须可以在取消拾取环境中导入,并且模块必须包含命名对象,否则将引发异常。 3

类似地,类被命名引用pickle,因此在取消拾取环境中应用相同的限制。注意,类的代码或数据都没有被pickle,因此在下面的示例中,class属性 attr 未在取消拾取环境中还原::

class Foo:
    attr = 'A class attribute'

picklestring = pickle.dumps(Foo)

这些限制是必须在模块的顶层定义可选取函数和类的原因。

同样,当类实例被pickle时,它们的类的代码和数据也不会被pickle一起被pickle。只有实例数据被pickle。这样做是有目的的,所以您可以修复类中的错误,或者向类中添加方法,并且仍然可以加载用该类的早期版本创建的对象。如果您计划拥有可以看到类的许多版本的长寿命对象,那么在对象中放置一个版本号可能是值得的,这样类的 __setstate__() 方法。

酸洗类实例

在本节中,我们将描述可用于定义、自定义和控制类实例如何被pickle和unpickle的一般机制。

在大多数情况下,不需要额外的代码来使实例可拾取。默认情况下,pickle将通过内省来检索类和实例的属性。当取消拾取类实例时, __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__() 方法可以指定传递给 __new__() 方法。方法必须返回一对 (args, kwargs) 在哪里? args 是位置参数和 关键字参数 用于构造对象的命名参数的字典。这些将被传递给 __new__() 方法。

如果 __new__() 类的方法只需要关键字参数。否则,建议实现兼容性 __getnewargs__() .

在 3.6 版更改: __getnewargs_ex__() 现在在协议2和3中使用。

object.__getnewargs__()

这种方法的作用类似于 __getnewargs_ex__() ,但只支持位置参数。它必须返回一组参数 args 将传递给 __new__() 方法。

__getnewargs__() 如果 __getnewargs_ex__() 定义。

在 3.6 版更改: 在python 3.6之前, __getnewargs__() 被调用而不是 __getnewargs_ex__() 在方案2和3中。

object.__getstate__()

类可以进一步影响其实例的pickle方式;如果类定义了方法 __getstate__() 将调用它并将返回的对象作为实例的内容而不是实例字典的内容进行pickle。如果 __getstate__() 方法不存在,实例的 __dict__ 像往常一样腌制。

object.__setstate__(state)

如果类定义了 __setstate__() ,以未拾取状态调用。在这种情况下,状态对象不需要是字典。否则,pickled状态必须是字典,并且其项将分配给新实例的字典。

注解

如果 __getstate__() 返回一个错误值, __setstate__() 取消拾取时不会调用方法。

请参阅本节 处理有状态对象 有关如何使用方法的详细信息 __getstate__()__setstate__() .

注解

在解压的时候,一些方法 __getattr__()__getattribute__()__setattr__() 可以根据实例调用。如果这些方法依赖于某些内部不变量为true,则类型应实现 __new__() 建立这样一个不变量,如 __init__() 在取消绑定实例时不调用。

正如我们将看到的,pickle不直接使用上述方法。实际上,这些方法是实现 __reduce__() 特殊方法。复制协议提供了一个统一的接口,用于检索酸洗和复制对象所需的数据。 4

虽然功能强大,但实现 __reduce__() 直接在类中是容易出错的。因此,类设计器应该使用高级接口(即, __getnewargs_ex__()__getstate__()__setstate__() )只要可能。但是,我们将展示使用 __reduce__() 是唯一的选择,或导致更有效的酸洗或两者兼而有之。

object.__reduce__()

接口当前定义如下。这个 __reduce__() 方法不带参数,应返回一个字符串或一个元组(返回的对象通常称为“reduce value”)。

如果返回字符串,则应将该字符串解释为全局变量的名称。它应该是对象相对于其模块的本地名称;pickle模块搜索模块命名空间以确定对象的模块。这种行为通常对单身人士有用。

返回元组时,它的长度必须介于2到6项之间。可选项目可以省略,或者 None 可以作为它们的值提供。每个项目的语义顺序如下:

  • 将被调用以创建对象的初始版本的可调用对象。

  • 可调用对象的一组参数。如果可调用文件不接受任何参数,则必须提供空元组。

  • (可选)对象的状态,该状态将传递给对象的 __setstate__() 方法如前所述。如果对象没有这样的方法,那么该值必须是字典,并且将添加到对象的 __dict__ 属性。

  • 可选地,一个迭代器(而不是序列)生成连续的项。这些项将使用 obj.append(item) 或者,批量使用 obj.extend(list_of_items) . 这主要用于列表子类,但也可以由其他类使用,只要它们有 append()extend() 方法具有适当的签名。(是否) append()extend() 是否使用取决于使用哪个pickle协议版本以及要附加的项目数,因此必须同时支持这两个版本。)

  • 可选地,一个迭代器(不是序列)生成连续的键值对。这些项将使用 obj[key] = value . 这主要用于字典子类,但也可以由其他类使用,只要它们实现 __setitem__() .

  • 或者,可调用的 (obj, state) 签名。此可调用允许用户以编程方式控制特定对象的状态更新行为,而不是使用 obj 的静态 __setstate__() 方法。如果没有 None ,此可调用文件将优先于 obj__setstate__() .

    3.8 新版功能: 可选的第六个元组项, (obj, state) ,已添加。

object.__reduce_ex__(protocol)

或者,a __reduce_ex__() 可以定义方法。唯一的区别是这个方法应该采用一个整数参数,即协议版本。如果定义好了,泡菜会比 __reduce__() 方法。此外, __reduce__() 自动成为扩展版本的同义词。此方法的主要用途是为旧的python版本提供向后兼容的reduce值。

外部对象的持久性

为了对象持久性的好处, pickle 模块支持对pickled数据流之外的对象的引用的概念。此类对象由持久ID引用,该持久ID应该是字母数字字符字符串(对于协议0) 5 或者只是一个任意对象(对于任何较新的协议)。

这种持久ID的解析不是由 pickle 模块;它将此解析委托给pickler和unpickler上的用户定义方法, persistent_id()persistent_load() 分别。

要pickle具有外部持久ID的对象,pickler必须有一个自定义 persistent_id() 将对象作为参数并返回 None 或该对象的持久ID。什么时候? None 返回时,pickler只是像正常一样对对象进行pickle。当返回持久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()

调度表

如果您希望自定义某些类的pickle而不干扰依赖pickle的任何其他代码,那么可以创建一个具有私有调度表的pickler。

由管理的全局调度表 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)

处理有状态对象

下面是一个示例,演示如何修改类的酸洗行为。这个 TextReader 类打开一个文本文件,并每次返回行号和行内容 readline() 方法被调用。如果A TextReader 实例被pickle,所有属性 除了 将保存文件对象成员。取消勾选实例后,文件将重新打开,并从最后一个位置恢复读取。这个 __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!'

类型、函数和其他对象的自定义缩减

3.8 新版功能.

有时候, dispatch_table 可能不够灵活。特别是,我们可能希望根据对象类型以外的其他标准定制pickle,或者我们可能希望定制函数和类的pickle。

对于这些情况,可以从 Pickler 类并实现 reducer_override() 方法。此方法可以返回任意约简元组(请参见 __reduce__() )它也可以返回 NotImplemented 回到传统的行为。

如果两者都 dispatch_tablereducer_override() 然后定义 reducer_override() 方法优先。

注解

出于性能原因, reducer_override() 不能为以下对象调用: NoneTrueFalse 以及 intfloatbytesstrdictsetfrozensetlisttuple .

下面是一个简单的例子,我们允许酸洗和重构一个给定的类:

import io
import pickle

class MyClass:
    my_attribute = 1

class MyPickler(pickle.Pickler):
    def reducer_override(self, obj):
        """Custom reducer for MyClass."""
        if getattr(obj, "__name__", None) == "MyClass":
            return type, (obj.__name__, obj.__bases__,
                          {'my_attribute': obj.my_attribute})
        else:
            # For any other object, fallback to usual reduction
            return NotImplemented

f = io.BytesIO()
p = MyPickler(f)
p.dump(MyClass)

del MyClass

unpickled_class = pickle.loads(f.getvalue())

assert isinstance(unpickled_class, type)
assert unpickled_class.__name__ == "MyClass"
assert unpickled_class.my_attribute == 1

带外缓冲器

3.8 新版功能.

在某些情况下, pickle 模块用于传输大量数据。因此,尽量减少内存副本的数量,以保持性能和资源消耗是很重要的。但是,正常运行的 pickle 模块将对象的图形结构转换为连续的字节流,本质上涉及将数据复制到pickle流和从pickle流复制数据。

如果 供应商 (要传输的对象类型的实现)和 消费者 (通信系统的实施)支持pickle协议5及更高版本提供的带外传输设施。

提供程序API

要pickle的大型数据对象必须实现 __reduce_ex__() 专门用于协议5及更高版本的方法,它返回 PickleBuffer 实例(而不是 bytes 对象)用于任何大数据。

A PickleBuffer 对象 信号 基础缓冲区可以进行带外数据传输。这些对象与 pickle 模块。然而,消费者也可以选择告诉 pickle 他们将自己处理这些缓冲区。

消费者API

通信系统可以对 PickleBuffer 序列化对象图时生成的对象。

在发送端,它需要通过 buffer_callback 参数 Pickler (或到 dump()dumps() 函数),它将与每个函数一起调用 PickleBuffer 在清除对象图时生成。缓冲区由 buffer_callback 不会看到他们的数据被复制到pickle流中,只会插入一个便宜的标记。

在接收端,它需要通过 缓冲器 参数 Unpickler (或到 load()loads() 函数),它是传递给 buffer_callback . iterable应该按照传递给的顺序生成缓冲区 buffer_callback . 这些缓冲区将提供由酸洗产生原始数据的对象的重建者所期望的数据。 PickleBuffer 物体。

在发送端和接收端之间,通信系统可以自由地为带外缓冲器实现自己的传输机制。潜在的优化包括使用共享内存或依赖于数据类型的压缩。

例子

下面是一个小例子,我们在其中实现 bytearray 能够参与带外缓冲酸洗的子类:

class ZeroCopyByteArray(bytearray):

    def __reduce_ex__(self, protocol):
        if protocol >= 5:
            return type(self)._reconstruct, (PickleBuffer(self),), None
        else:
            # PickleBuffer is forbidden with pickle protocols <= 4.
            return type(self)._reconstruct, (bytearray(self),)

    @classmethod
    def _reconstruct(cls, obj):
        with memoryview(obj) as m:
            # Get a handle over the original buffer object
            obj = m.obj
            if type(obj) is cls:
                # Original buffer object is a ZeroCopyByteArray, return it
                # as-is.
                return obj
            else:
                return cls(obj)

重建者 _reconstruct 类方法)返回缓冲区提供的对象(如果其类型正确)。这是一个在这个玩具例子中模拟零拷贝行为的简单方法。

在消费者方面,我们可以按照通常的方式来处理这些对象,当未细分时,会给我们一份原始对象的副本:

b = ZeroCopyByteArray(b"abc")
data = pickle.dumps(b, protocol=5)
new_b = pickle.loads(data)
print(b == new_b)  # True
print(b is new_b)  # False: a copy was made

但如果我们通过 buffer_callback 然后在未序列化时返回累积的缓冲区,我们可以返回原始对象:

b = ZeroCopyByteArray(b"abc")
buffers = []
data = pickle.dumps(b, protocol=5, buffer_callback=buffers.append)
new_b = pickle.loads(data, buffers=buffers)
print(b == new_b)  # True
print(b is new_b)  # True: no copy was made

这个例子受到以下事实的限制: bytearray 分配自己的内存:不能创建 bytearray 由另一个对象的内存支持的实例。但是,第三方数据类型(如numpy数组)没有这个限制,并且允许在不同的进程或系统之间传输时使用零拷贝酸洗(或尽可能少地复制)。

参见

PEP 574 --带外数据的pickle协议5

限制全局

默认情况下,取消拾取将导入它在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() 每当请求全局(即类或函数)时调用。因此,既可以完全禁止全局变量,也可以将其限制在安全的子集中。

下面是一个不允许少数安全类从 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()

我们的开卷机工作的一个示例用途是:

>>> 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 或第三方解决方案。

性能

最新版本的pickle协议(来自协议2和更高版本)为几种常见功能和内置类型提供了高效的二进制编码。此外,还有 pickle 模块有一个用C编写的透明优化器。

实例

对于最简单的代码,请使用 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)

下面的示例读取得到的pickled数据。::

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

用于处理和分析酸洗数据的工具。

模块 shelve

对象的索引数据库;使用 pickle .

模块 copy

浅和深对象复制。

模块 marshal

内置类型的高性能序列化。

脚注

1

不要把这个和 marshal 模块

2

这就是为什么 lambda 无法pickle函数:全部 lambda 函数共享相同的名称: <lambda> .

3

引发的异常可能是 ImportErrorAttributeError 但它可能是其他东西。

4

这个 copy 模块将此协议用于浅层和深层复制操作。

5

字母数字字符的限制是由于协议0中的持久ID由换行符分隔。因此,如果在持久ID中出现任何类型的换行符,那么结果pickle将变得不可读。