python / 3.7.2rc1 / all / library-pickle.html

pickle — Python 对象序列化

源代码: Lib/pickle.py


pickle模块实现用于对 Python 对象结构进行序列化和反序列化的二进制协议。 *“ Pickling” 是将 Python 对象层次结构转换为字节流的过程,而“ unpickling” *是逆运算,将字节流(从binary filebytes-like object)转换回对象层次结构的过程。Pickling(和 Pickling)也称为“序列化”,“编组”或“展平”;但是,为避免混淆,此处使用的术语是“Pickling”和“未 Pickling”。

Warning

pickle模块 不安全 。仅释放您信任的数据。

可以构造恶意的腌制数据,这些数据将在“解腌期间”执行**代码。永远不要挑剔那些可能来自不可信来源或可能被篡改的数据。

如果您需要确保数据未被篡改,请考虑使用hmac签名。

如果要处理不受信任的数据,则更安全的序列化格式(例如json)可能更合适。参见与 json 的比较

与其他 Python 模块的关系

与 marshal 的比较

Python 有一个更原始的序列化模块marshal,但总的来说pickle应该始终是序列化 Python 对象的首选方式。 marshal主要用于支持 Python 的.pyc文件。

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

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

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

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

  • 不能保证marshal序列化格式可跨 Python 版本移植。因为它的主要工作就是支持.pyc文件,所以 Python 实现者保留在需要时以非向后兼容的方式更改序列化格式的权利。如果您选择了兼容的 pickle 协议,并且如果您的数据越过了唯一的突破性更改语言边界,则 pickle 和 unpickling 的代码可以处理 Python 2 到 Python 3 类型的差异,则pickle序列化格式可以保证在 Python 版本之间向后兼容。

与 json 的比较

pickle 协议和JSON(JavaScript 对象表示法)之间存在根本差异:

  • JSON 是一种文本序列化格式(尽管大多数情况下随后将其编码为utf-8,但它输出 unicode 文本),而 pickle 是二进制序列化格式;

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

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

  • 默认情况下,JSON 只能表示 Python 内置类型的子集,而不能表示自定义类。 pickle 可以表示大量的 Python 类型(pass巧妙地使用 Python 的自省Function,许多自动类型;pass实现特定对象 API可以解决复杂的情况);

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

See also

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

数据流格式

pickle使用的数据格式特定于 Python。这样做的优点是不受外部标准(例如 JSON 或 XDR)的限制(不能表示指针共享);但是,这意味着非 Python 程序可能无法重建腌制的 Python 对象。

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

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

当前有 6 种不同的协议可用于 Pickling。使用的协议越高,读取生成的 pickle 所需的 Python 版本越新。

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

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

  • 协议版本 2 是在 Python 2.3 中引入的。它提供了更有效的new-style class esPickling。有关协议 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

Note

序列化是比持久性更原始的概念。尽管pickle读写文件对象,但是它不能处理持久对象的命名问题,也不能处理(甚至更复杂的)并发访问持久对象的问题。 pickle模块可以将复杂的对象转换为字节流,并且可以将字节流转换为具有相同内部结构的对象。也许与这些字节流有关的最明显的事情是将它们写入文件中,但是也可以考虑pass网络发送它们或将它们存储在数据库中。 shelve模块提供了一个简单的界面,可以对 DBM 风格的数据库文件中的对象进行腌制和解腌。

Module Interface

要序列化对象层次结构,只需调用dumps()函数。同样,要反序列化数据流,请调用loads()函数。但是,如果要更多地控制序列化和反序列化,则可以分别创建PicklerUnpickler对象。

pickle模块提供以下常量:

  • pickle. HIGHEST_PROTOCOL

  • pickle. DEFAULT_PROTOCOL

    • 一个整数,默认值protocol version用于 Pickling。可能小于HIGHEST_PROTOCOL。当前的默认协议是 4,该协议最初是在 Python 3.4 中引入的,并且与以前的版本不兼容。

在版本 3.0 中更改:默认协议为 3.

在版本 3.8 中更改:默认协议为 4.

pickle模块提供以下Function,以使 Pickling 过程更加方便:

  • pickle. dump(* obj file protocol = None **,* fix_imports = True buffer_callback = None *)
    • 将对象* obj *的腌制表示形式写入打开的file object * file *中。这等效于Pickler(file, protocol).dump(obj)

参数* file protocol fix_imports buffer_callback *具有与Pickler构造函数相同的含义。

在 3.8 版中进行了更改:添加了* buffer_callback *参数。

  • pickle. dumps(* obj protocol = None **,* fix_imports = True buffer_callback = None *)
    • 将对象* obj *的腌制表示形式返回为bytes对象,而不是将其写入文件中。

参数* protocol fix_imports buffer_callback *具有与Pickler构造函数相同的含义。

在 3.8 版中进行了更改:添加了* buffer_callback *参数。

  • pickle. load(* file **,* fix_imports = True encoding =“ ASCII” errors =“ strict” buffers = None *)
    • 从打开的file object * file *中读取对象的腌制表示,并返回其中指定的重构对象层次结构。这等效于Unpickler(file).load()

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

参数* file fix_imports encoding errors strict buffers *具有与Unpickler构造函数相同的含义。

在 3.8 版中进行了更改:添加了* buffers *参数。

  • pickle. loads(* data **,* fix_imports = True encoding =“ ASCII” errors =“ strict” buffers = None *)
    • 返回对象的腌制表示形式* data *的重构对象层次结构。 * data *必须为bytes-like object

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

参数* file fix_imports encoding errors strict buffers *具有与Unpickler构造函数相同的含义。

在 3.8 版中进行了更改:添加了* buffers *参数。

pickle模块定义了三个 exception:

  • exception pickle. PickleError

    • 其他 Picklingexception 的通用 Base Class。它继承了Exception
  • exception pickle. PicklingError

请参阅什么可以腌制和不腌制?以了解可以腌制哪些类型的对象。

  • exception pickle. UnpicklingError
    • 解开对象时出现问题,例如数据损坏或安全冲突,将引发错误。它继承了PickleError

请注意,在拆箱过程中还可能引发其他异常,包括(但不一定限于)AttributeError,EOFError,ImportError 和 IndexError。

pickle模块导出三个类PicklerUnpicklerPickleBuffer

    • class * pickle. Pickler(* file protocol = None **,* fix_imports = True buffer_callback = None *)
    • 这需要一个二进制文件来写入 pickle 数据流。

可选的* protocol *参数,一个整数,告诉 selectors 使用给定的协议。支持的协议是 0 到HIGHEST_PROTOCOL。如果未指定,则默认值为DEFAULT_PROTOCOL。如果指定负数,则选择HIGHEST_PROTOCOL

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

如果* fix_imports 为 true 并且 protocol *小于 3,则 pickle 将try将新的 Python 3 名称 Map 到 Python 2 中使用的旧模块名称,以便 pickle 数据流可被 Python 2 读取。

如果* buffer_callback 为 None(默认值),则将缓冲区视图序列化为 file *作为 pickle 流的一部分。

如果* buffer_callback *不为 None,则可以在缓冲区视图中多次调用它。如果回调返回一个假值(例如 None),则给定的缓冲区为out-of-band;否则,缓冲区将在带内(即在 pickle 流中)进行序列化。

如果* buffer_callback 不为 None 并且 protocol *为 None 或小于 5,则为错误。

在 3.8 版中进行了更改:添加了* buffer_callback *参数。

  • 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()进行语句。它是一个 Map,其键是类,其值是归约函数。约简函数采用关联类的单个参数,并且应遵循与reduce()方法相同的接口。

默认情况下,pickler 对象将不具有dispatch_table属性,而是使用copyreg模块 Management 的全局调度表。但是,要自定义特定腌制对象的腌制,可以将dispatch_table属性设置为类似 dict 的对象。或者,如果Pickler的子类具有dispatch_table属性,则它将用作该类实例的默认调度表。

有关用法示例,请参见Dispatch Tables

版本 3.3 中的新Function。

  • reducer_override(* self obj *)
    • 可以在Pickler子类中定义的特殊 reducer。此方法的优先级高于dispatch_table中的任何 reducer。它应与reduce()方法遵循相同的接口,并且可以选择将NotImplemented返回到dispatch_table注册的 reducers 上以回退到 pickle obj

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

3.8 版的新Function。

  • fast
    • 不推荐使用。如果设置为真值,则启用快速模式。快速模式禁用了备忘录的使用,因此pass不生成多余的 PUT 操作码来加快 Pickling 过程。不应将其与自引用对象一起使用,否则将导致Pickler无限递归。

如果需要更紧凑的 pickle,请使用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 encoding errors 用于控制对 Python 2 生成的 pickle 流的兼容性支持。如果 fix_imports 为 true,pickle 将try将旧的 Python 2 名称 Map 到使用的新名称。在 Python 3 中. encoding errors *告诉 pickle 如何解码 Python 2 腌制的 8 位字符串实例;它们分别默认为“ ASCII”和“ strict”。 * encoding *可以是“字节”,以将这些 8 位字符串实例读取为字节对象。需要使用encoding='latin1'来解开 NumPy 数组以及被 Python 2 腌制的datetimedatetime的实例。

如果* buffers 为 None(默认值),则反序列化所需的所有数据都必须包含在 pickle 流中。这意味着在实例化Pickler时(或在调用dump()dumps()时) buffer_callback *参数为 None。

如果* buffers 不为 None,则每次腌制流引用out-of-band缓冲区视图时都要使用已启用缓冲区的对象的可迭代方法。此类缓冲区是为了给 Pickler 对象的 buffer_callback *提供的。

在 3.8 版中进行了更改:添加了* buffers *参数。

  • load ( )

    • 从构造函数中提供的打开文件对象中读取对象的腌制表示,然后返回其中指定的重构对象层次结构。腌制过的对象表示之后的字节将被忽略。
  • persistent_load(* pid *)

如果已定义,则persistent_load()应返回由持久 ID * pid *指定的对象。如果遇到无效的永久 ID,则应引发UnpicklingError

有关详细信息和用法示例,请参见外部对象的持久性

  • find_class(* module name *)
    • 如有必要,导入* module 并从中返回名为 name 的对象,其中 module name *参数是str个对象。注意,与其名称不同,find_class()也用于查找函数。

子类可以重写此方法,以控制什么类型的对象以及如何加载对象,从而有可能降低安全风险。有关详情,请参阅Restricting Globals

用参数modulename引发auditing event pickle.find_class

    • class * pickle. PickleBuffer(* buffer *)

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

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

3.8 版的新Function。

  • raw ( )

    • 返回此缓冲区下面的存储区的memoryview。返回的对象是格式为B(无符号字节)的一维 C 连续 memoryview。如果缓冲区既不是 C-也不是 Fortran 相邻的,则引发BufferError
  • release ( )

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

什么可以腌制和不腌制?

可以腌制以下类型:

  • NoneTrueFalse

  • 整数,浮点数,复数

  • 字符串,字节,字节数组

  • 仅包含可腌制对象的 Tuples,列表,集合和词典

  • 在模块的顶层定义的Function(使用def而不是lambda)

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

  • 在模块顶层定义的类

  • 此类的实例,它们的dict或调用getstate()的结果是可腌制的(有关详细信息,请参见Pickling 类实例部分)。

try腌制不可腌制的物体将引发PicklingError异常;发生这种情况时,未指定的字节数可能已被写入基础文件。try腌制高度递归的数据结构可能会超出最大递归深度,在这种情况下将引发RecursionError。您可以使用sys.setrecursionlimit()小心地提高此限制。

请注意,函数(内置的和用户定义的)是pass“完全限定”的名称引用而不是值进行 Pickling 的。 [2]这意味着仅对函数名称以及定义该函数的模块名称进行了 Pickling。不对函数的代码或其任何函数属性进行 Pickling。因此,定义模块必须在解酸环境中是可导入的,并且该模块必须包含命名对象,否则将引发异常。 [3]

同样,pass命名引用对类进行 Pickling,因此在未 Pickling 环境中也适用相同的限制。请注意,没有对类的代码或数据进行 Pickling,因此在下面的示例中,在未 Pickling 的环境中未还原类属性attr

class Foo:
    attr = 'A class attribute'

picklestring = pickle.dumps(Foo)

这些限制是为什么必须在模块的顶层定义可腌制函数和类的原因。

同样,当对类实例进行腌制时,其类的代码和数据也不会与其一起被腌制。仅实例数据被腌制。这样做是有意为之的,因此您可以修复类中的错误或向类中添加方法,并仍然加载使用该类的早期版本创建的对象。如果计划使用寿命很长的对象,而该对象将看到类的许多版本,则可能值得在对象中放入版本号,以便可以pass类的setstate()方法进行适当的转换。

Pickling 类实例

在本节中,我们描述了可用于定义,自定义和控制如何腌制和取消腌制类实例的通用机制。

在大多数情况下,不需要其他代码即可使实例可腌制。默认情况下,pickle 将pass自省检索实例的类和属性。解开一个类实例时,通常不调用其init()方法。默认行为首先创建一个未初始化的实例,然后还原保存的属性。下面的代码显示了此行为的实现:

def save(obj):
    return (obj.__class__, obj.__dict__)

def load(cls, attributes):
    obj = cls.__new__(cls)
    obj.__dict__.update(attributes)
    return obj

类可以pass提供一种或几种特殊方法来更改默认行为:

  • object. __getnewargs_ex__ ( )
    • 在协议 2 和更新的协议中,实现getnewargs_ex()方法的类可以规定解开时传递给new()方法的值。该方法必须返回Pair(args, kwargs),其中* args 是位置参数的 Tuples, kwargs *是用于构造对象的命名参数的字典。取消拾取后,这些将被传递给new()方法。

如果您类的new()方法需要仅使用关键字的参数,则应实现此方法。否则,建议实现getnewargs()以实现兼容性。

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

  • object. __getnewargs__ ( )
    • 此方法的作用与getnewargs_ex()相似,但仅支持位置参数。它必须返回一个 Tuplesargs的 Tuples,该 Tuples 将在拆开后传递给new()方法。

如果定义了getnewargs_ex(),则不会调用getnewargs()

在 3.6 版中进行了更改:在 Python 3.6 之前,协议 2 和 3 中调用getnewargs()而不是getnewargs_ex()

  • object. __getstate__ ( )

    • 类可以进一步影响其实例的腌制方式。如果该类定义了方法getstate(),则会调用该方法,并将返回的对象腌制为实例的内容,而不是实例的字典的内容。如果没有getstate()方法,则照常对实例的dict进行腌制。
  • object. __setstate__(* state *)

    • 取消拾取后,如果该类定义了setstate(),则会以未拾取状态调用它。在那种情况下,不需要状态对象是字典。否则,腌制状态必须是字典,并且其项将分配给新实例的字典。

Note

如果getstate()返回的是假值,则取消提取时不会调用setstate()方法。

有关如何使用方法getstate()setstate()的更多信息,请参见处理有状态对象部分。

Note

在解开时间,可能会在实例上调用诸如getattr()getattribute()setattr()之类的方法。如果那些方法依赖于某个内部不变式为真,则该类型应实现new()以构建这样的不变式,因为取消实例化时不会调用init()

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

尽管Function强大,但是直接在您的类中实现reduce()容易出错。因此,类设计人员应尽可能使用高级接口(即getnewargs_ex()getstate()setstate())。但是,我们将显示使用reduce()是唯一选择或导致更有效的 Pickling 或同时使用两者的情况。

  • object. __reduce__ ( )
    • 该接口当前定义如下。 reduce()方法不带参数,并且应返回字符串或最好返回一个 Tuples(返回的对象通常称为“减少值”)。

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

返回 Tuples 时,它的长度必须在 2 到 6 之间。可选项目可以Ellipsis,也可以提供None作为其值。每个项目的语义 Sequences 如下:

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

  • 可调用对象的参数 Tuples。如果可调用对象不接受任何参数,则必须给出一个空的 Tuples。

  • (可选)对象的状态,如前所述,该状态将传递给对象的setstate()方法。如果对象没有这种方法,则该值必须是字典,并将其添加到对象的dict属性中。

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

  • 可选地,迭代器(不是序列)产生连续的键值对。这些项目将使用obj[key] = value存储到对象。这主要用于字典子类,但其他类可以实现setitem(),但可以使用它们。

  • (可选)带有(obj, state)签名的可调用项。此可调用对象允许用户以编程方式控制特定对象的状态更新行为,而不是使用obj的静态setstate()方法。如果不是None,则此可调用对象的优先级高于objsetstate()

版本 3.8 中的新增Function:添加了可选的第六 Tuples 项(obj, state)

  • object. __reduce_ex__(* protocol *)
    • 或者,可以定义reduce_ex()方法。唯一的区别是此方法应采用单个整数参数,即协议版本。定义后,pickle 将比reduce()方法更喜欢它。另外,reduce()自动成为扩展版本的同义词。此方法的主要用途是为较旧的 Python 版本提供向后兼容的 reduce 值。

外部对象的持久性

为了实现对象持久性,pickle模块支持对腌制数据流外部的对象进行引用的概念。此类对象由持久 ID 引用,该 ID 应该是一串字母数字字符(对于协议 0)[5],也可以是任意对象(对于任何较新的协议)。

pickle模块未定义此类持久 ID 的解析;它将把此分辨率委派给分别在persistent_id()persistent_load()上的 picker 和 unpickler 上用户定义的方法。

要使具有外部持久性 ID 的对象腌制,selectors 必须具有一个自定义persistent_id()方法,该方法将对象作为参数并返回None或该对象的持久性 ID。当返回None时,selectors 将简单地照常对对象进行 Pickling。返回持久性 ID 字符串后,selectors 将对该对象以及标记进行腌制,以便取消 selectors 将其识别为持久性 ID。

要解除对外部对象的限制,解除请求者必须具有一个自定义persistent_load()方法,该方法接受一个持久 ID 对象并返回引用的对象。

这是一个综合示例,展示了如何pass引用使用持久性 ID 来腌制外部对象。

# 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()

Dispatch Tables

如果要自定义某些类的 Pickling 而不打扰其他依赖于 Pickling 的代码,则可以创建一个带有专用调度表的 Pickling 机。

copyreg模块 Management 的全局调度表可作为copyreg.dispatch_table使用。因此,可以选择将copyreg.dispatch_table的修改后的副本用作私有调度表。

For example

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)

处理状态对象

这是显示如何修改类的 Pickling 行为的示例。 TextReader类打开一个文本文件,并在每次调用readline()方法时返回行号和行内容。如果腌制了一个TextReader实例,则保存文件对象成员的所有属性(除外)。取消实例化后,将重新打开文件,并从最后一个位置恢复读取。 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 版的新Function。

有时dispatch_table可能不够灵活。特别是,我们可能希望基于除对象类型之外的其他标准来定制 Pickling,或者我们可能想要定制函数和类的 Pickling。

对于这些情况,可以从Pickler类继承子类并实现reducer_override()方法。此方法可以返回任意的约简 Tuples(请参见reduce())。它可以替代地使NotImplemented退回到传统行为。

如果同时定义了dispatch_tablereducer_override(),则reducer_override()方法优先。

Note

出于性能原因,可能不会为以下对象调用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

Out-of-band Buffers

3.8 版的新Function。

在某些情况下,pickle模块用于传输大量数据。因此,重要的是最小化内存副本的数量,以保持性能和资源消耗。但是,pickle模块的正常操作会在将对象的图形结构转换为 Sequences 的字节流时,从本质上讲涉及到与 Pickling 流之间复制数据。

如果* provider (要传输的对象类型的实现)和 consumer *(通信系统的实现)都支持 pickle 协议 5 和 pickle 协议 5 提供的带外传输Function,则可以避免此约束。更高。

Provider API

要腌制的大数据对象必须实现专用于协议 5 及更高版本的reduce_ex()方法,该方法将为任何大数据返回PickleBuffer实例(而不是bytes对象)。

一个PickleBuffer对象,向底层缓冲区有资格进行带外数据传输的 signal。这些对象与pickle模块的常规用法保持兼容。但是,使用者也可以选择告诉pickle他们将自己处理这些缓冲区。

Consumer API

通信系统可以实现对对象图进行序列化时生成的PickleBuffer对象的自定义处理。

在发送端,它需要将* buffer_callback *参数传递给Pickler(或dump()dumps()函数),该参数将在对对象图进行 Pickling 时生成的每个PickleBuffer中调用。 * buffer_callback *所累积的缓冲区不会看到其数据被复制到 pickle 流中,只会插入便宜的标记。

在接收端,它需要将* buffers 参数传递给Unpickler(或传递给load()loads()函数),这是传递给 buffer_callback 的缓冲区的可迭代方法。可迭代的缓冲区应按传递给 buffer_callback *的 Sequences 生成缓冲区。这些缓冲区将提供对象的重构器期望的数据,这些对象的 Pickling 产生了原始的PickleBuffer对象。

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

Example

这是一个简单的示例,其中我们实现了一个bytearray子类,该子类能够参与带外缓冲区 Pickling:

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类方法)将返回缓冲区的提供对象。这是在此玩具示例上模拟零复制行为的简便方法。

在 Consumer 方面,我们可以用通常的方法来腌制那些对象,当将其反序列化时,将为我们提供原始对象的副本:

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 数组)没有此限制,并且在不同的流程或系统之间进行传输时,允许使用零复制 Pickling(或制作尽可能少的副本)。

See also

PEP 574 –带有带外数据的 Pickle 协议 5

Restricting Globals

默认情况下,取消腌制将导入它在腌制数据中找到的任何类或函数。对于许多应用程序,此行为是不可接受的,因为它允许取消提取程序导入和调用任意代码。只要考虑一下这个手工制作的 pickle 数据流在加载时会做什么:

>>> import pickle
>>> pickle.loads(b"cos\nsystem\n(S'echo hello world'\ntR.")
hello world
0

在此示例中,取消拾取程序导入os.system()函数,然后应用字符串参数“ echo hello world”。尽管此示例没有冒犯性,但不难想象会损坏系统的示例。

因此,您可能需要pass自定义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()

我们的无 Pickling 工作的示例用法旨在:

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

Performance

pickle 协议的最新版本(从协议 2 起)具有针对几种常见Function和内置类型的有效二进制编码。另外,pickle模块具有用 C 编写的透明优化器。

Examples

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

See also

  • Module copyreg

  • 扩展类型的 Pickle 接口构造函数注册。

  • Module pickletools

  • 用于处理和分析腌制数据的工具。

  • Module shelve

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

  • Module copy

  • 浅而深的对象复制。

  • Module marshal

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

Footnotes

  • [1]

    • 不要将此与marshal模块混淆
  • [2]

    • 这就是为什么不能腌制lambda函数的原因:所有lambda函数共享相同的名称:<lambda>
  • [3]

  • [4]

    • copy模块使用此协议进行浅层和深层复制操作。
  • [5]

    • 字母数字字符的限制是由于协议 0 中的永久 ID 由换行符分隔的事实。因此,如果持久性 ID 中出现任何换行符,则产生的 pickle 将变得不可读。