"pickle" --- Python 对象序列化
******************************

**源代码：** Lib/pickle.py

======================================================================

"pickle" 实现了对一个 Python 对象结构的二进制序列化和反序列化。 *"封存
"* 是将 Python 对象及其所拥有的层次结构转化为一个字节流的过程，而 *"解
封"* 是相反的操作，会将（来自一个 *binary file* 或 *bytes-like object*
的）字节流转回一个对象层次结构。 封存（和解封）也可称为 "序列化", "编
组", [1] 或 "展平"；不过，为了避免混乱，这里使用的术语是 "封存" 和 "解
封"。

警告:

  "pickle" 模块 **并不安全**。 你只应该对你信任的数据进行 unpickle 操
  作。构建恶意的 pickle 数据来 **在解封时执行任意代码** 是可能的。 绝
  对不要对不信任来源的数据和可能被篡改过的数据进行解封。请考虑使用
  "hmac" 来对数据进行签名，确保数据没有被篡改。在你处理不信任数据时，
  更安全的序列化格式如 "json" 可能更为适合。参见  与 json 模块的比较
  。


与其他 Python 模块间的关系
==========================


与 "marshal" 间的关系
---------------------

Python 有一个更原生态的序列化模块称为 "marshal"，但通常 "pickle" 总是
应当作为序列化 Python 对象的首选方式。 "marshal" 的存在主要是为了支持
Python 的 ".pyc" 文件。

"pickle" 模块与 "marshal" 有几个显著的区别：

* "marshal" cannot be used to serialize user-defined classes and their
  instances.  "pickle" can save and restore class instances
  transparently, however the class definition must be importable and
  live in the same module as when the object was pickled.

* "marshal" 序列化格式不保证能跨 Python 版本移植。 因为它的主要任务是
  支持 ".pyc" 文件，Python 的实现方保留了在有必要时以非向下兼容的方式
  修改序列化格式的权利。 "pickle" 序列化格式则保证能跨 Python 发布版本
  提供向下兼容，其前提是选择兼容的 pickle 协议并且封存和解封代码针对从
  Python 2 到 Python 3 的类型差异进行了处理，如果你的数据跨越了这条破
  坏兼容性的语言特性更改边界的话。


与 "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序列化和反序列化的标准库模块


数据流格式
==========

The data format used by "pickle" is Python-specific.  This has the
advantage that there are no restrictions imposed by external standards
such as JSON (which can't represent pointer sharing); however it means
that non-Python programs may not be able to reconstruct pickled Python
objects.

By default, the "pickle" data format uses a relatively compact binary
representation.  If you need optimal size characteristics, you can
efficiently compress pickled data.

The module "pickletools" contains tools for analyzing data streams
generated by "pickle".  "pickletools" source code has extensive
comments about opcodes used by pickle protocols.

当前共有 6 种不同的协议可用于封存操作。 使用的协议版本越高，读取所生成
pickle 对象所需的 Python 版本就要越新。

* v0 版协议是原始的“人类可读”协议，并且向后兼容早期版本的 Python。

* v1 版协议是较早的二进制格式，它也与早期版本的 Python 兼容。

* 第 2 版协议是在 Python 2.3 中引入的。 它为 *新式类* 提供了更高效的封
  存机制。 请参考 **PEP 307** 了解第 2 版协议带来的改进的相关信息。

* v3 版协议是在 Python 3.0 中引入的。 它显式地支持 "bytes" 字节对象，
  不能使用 Python 2.x 解封。这是 Python 3.0-3.7 的默认协议。

* v4 版协议添加于 Python 3.4。它支持存储非常大的对象，能存储更多种类的
  对象，还包括一些针对数据格式的优化。这是 Python 3.8--3.13中使用的默
  认协议。有关第 4 版协议带来改进的信息，请参阅 **PEP 3154**。

* 第 5 版协议是在 Python 3.8 中加入的。 它增加了对带外数据的支持，并可
  加速带内数据处理。 它是自 Python 3.14 起的默认协议。请参阅 **PEP
  574** 了解第 5 版协议所带来的改进的详情。

备注:

  Serialization is a more primitive notion than persistence; although
  "pickle" reads and writes file objects, it does not handle the issue
  of naming persistent objects, nor the (even more complicated) issue
  of concurrent access to persistent objects.  The "pickle" module can
  transform a complex object into a byte stream and it can transform
  the byte stream into an object with the same internal structure.
  Perhaps the most obvious thing to do with these byte streams is to
  write them onto a file, but it is also conceivable to send them
  across a network or store them in a database.  The "shelve" module
  provides a simple interface to pickle and unpickle objects on DBM-
  style database files.


模块接口
========

要序列化某个包含层次结构的对象，只需调用 "dumps()" 函数即可。同样，要
反序列化数据流，可以调用 "loads()" 函数。但是，如果要对序列化和反序列
化加以更多的控制，可以分别创建 "Pickler" 或 "Unpickler" 对象。

"pickle" 模块提供了下列常量：

pickle.HIGHEST_PROTOCOL

   整数，可用的最高 协议版本。此值可以作为 *协议* 值传递给 "dump()" 和
   "dumps()" 函数，以及 "Pickler" 的构造函数。

pickle.DEFAULT_PROTOCOL

   整数，用于 pickle 数据的默认 协议版本。 它可能小于
   "HIGHEST_PROTOCOL"。当前默认协议版本是 5，它在 Python 3.8 中引入，
   与之前的版本不兼容。此版本引入了对带外缓冲区的支持，其中 **PEP
   3118** 兼容的数据可以与主 pickle 流分开传输。

   在 3.0 版本发生变更: 默认协议版本是 3。

   在 3.8 版本发生变更: 默认协议版本是 4。

   在 3.14 版本发生变更: 默认的协议版本是 5。

"pickle" 模块提供了下列函数以使封存过程更为便捷：

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* *文件* 中读取封存后的对象，重建其中特定对
   象的层次结构并返回。它相当于 "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 协议版本是自动检测出来的，所以不需要参数来指定协议。封存对象
   以外的其他字节将被忽略。

   参数 *fix_imports*, *encoding*, *errors*, *strict* 和 *buffers* 的
   含义与在 "Unpickler" 构造器中的含义相同。

   在 3.8 版本发生变更: 加入了 *buffers* 参数。

"pickle" 模块定义了三个异常：

exception pickle.PickleError

   其他 pickle 异常的共同基类。 它继承自 "Exception"。

exception pickle.PicklingError

   当 "Pickler" 遇到无法封存的对象时将引发的错误。 它继承自
   "PickleError"。

   参考 可以被封存/解封的对象 来了解哪些对象可以被封存。

exception pickle.UnpicklingError

   当解封对象出现问题时将引发的错误，例如数据损坏或违反安全规则。 它继
   承自 "PickleError"。

   注意，解封时可能还会抛出其他异常，包括（但不限于） AttributeError、
   EOFError、ImportError 和 IndexError。

"pickle" 模块导出了三个类，"Pickler", "Unpickler" 和 "PickleBuffer":

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

   它接受一个二进制文件用于写入 pickle 数据流。

   可选参数 *protocol* 是一个整数，告知 pickler 使用指定的协议，可选择
   的协议范围从 0 到 "HIGHEST_PROTOCOL"。如果没有指定，这一参数默认值
   为 "DEFAULT_PROTOCOL"。指定一个负数就相当于指定 "HIGHEST_PROTOCOL"
   。

   参数 *file* 必须有一个 write() 方法，该 write() 方法要能接收字节作
   为其唯一参数。因此，它可以是一个打开的磁盘文件（用于写入二进制内容
   ），也可以是一个 "io.BytesIO" 实例，也可以是满足这一接口的其他任何
   自定义对象。

   如果 *fix_imports* 为 True 且 *protocol* 小于 3，pickle 将尝试将
   Python 3 中的新名称映射到 Python 2 中的旧模块名称，因此 Python 2 也
   可以读取封存的数据流。

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

   如果 *buffer_callback* 不为 "None"，那它可以用缓冲区视图调用任意次
   。 如果某次调用返回了假值 (例如 "None")，则给定的缓冲区是 带外的；
   在其他情况下缓冲区是带内的，例如在 pickle 流内部。

   如果 *buffer_callback* 不为 "None" 且 *protocol* 为 "None" 或小于 5
   则将出错。

   在 3.8 版本发生变更: 加入了 *buffer_callback* 参数。

   dump(obj)

      将 *obj* 封存后的内容写入已打开的文件对象，该文件对象已经在构造
      函数中指定。

   persistent_id(obj)

      默认无动作，该方法可被子类重写。

      如果 "persistent_id()" 返回 "None"，*obj* 会被照常 pickle。如果
      返回其他值，"Pickler" 会将这个函数的返回值作为 *obj* 的持久化 ID
      （Pickler 本应得到序列化数据流并将其写入文件，若此函数有返回值，
      则得到此函数的返回值并写入文件）。这个持久化 ID 的解释应当定义在
      "Unpickler.persistent_load()" 中（该方法定义还原对象的过程，并返
      回得到的对象）。注意，"persistent_id()" 的返回值本身不能拥有持久
      化 ID。

      参阅 持久化外部对象 获取详情和使用示例。

      在 3.13 版本发生变更: 在 "Pickler" 的 C 实现中添加此方法的默认实
      现。

   dispatch_table

      pickler 对象的 dispatch 表是对 *reduction 函数* 的注册，其类别可
      使用 "copyreg.pickle()" 来声明。 它本身是一个以类为键并以
      reduction 函数为值的映射。 一个 reduction 函数接受单个参数即其所
      关联的类并应当遵循与 "__reduce__()" 方法相同的接口。

      Pickler 对象默认并没有 "dispatch_table" 属性，该对象默认使用
      "copyreg" 模块中定义的全局 dispatch 表。如果要为特定 Pickler 对
      象自定义序列化过程，可以将 "dispatch_table" 属性设置为类字典对象
      （dict-like object）。另外，如果 "Pickler" 的子类设置了
      "dispatch_table" 属性，则该子类的实例会使用这个表作为默认的
      dispatch 表。

      参阅 Dispatch 表 获取使用示例。

      Added in version 3.3.

   reducer_override(obj)

      可以在 "Pickler" 子类中定义的特殊 reducer。 该方法的优先级高于
      "dispatch_table" 中的任何 reducer。 它应当遵循与 "__reduce__()"
      方法相同的接口，也可以选择返回 "NotImplemented" 以回退到使用
      "dispatch_table" 注册的 reducer 来封存 "obj"。

      参阅 类型，函数和其他对象的自定义归约 获取详细的示例。

      Added in version 3.8.

   fast

      已弃用。设为 True 则启用快速模式。快速模式禁用了“备忘录” (memo)
      的使用，即不生成多余的 PUT 操作码来加快封存过程。不应将其与自指
      (self-referential) 对象一起使用，否则将导致 "Pickler" 无限递归。

      如果需要进一步提高 pickle 的压缩率，请使用
      "pickletools.optimize()"。

   clear_memo()

      清空 pickler 的 "memo"。

      memo 是一种数据结构，用来记忆 pickler 已看过的对象，这样共享的或
      递归的对象将按引用而不是按值被 pickle。 此方法在重用 pickler 时
      很有用处。

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 将尝试将旧的 Python 2 名称映射到 Python 3 中对应的新名称。
   *encoding* 和 *errors* 参数告诉 pickle 如何解码 Python 2 存储的 8
   位字符串实例；这两个参数默认分别为 'ASCII' 和 'strict'。*encoding*
   参数可置为 'bytes' 来将这些 8 位字符串实例读取为字节对象。读取
   NumPy array 和 Python 2 存储的 "datetime"、"date" 和 "time" 实例时
   ，请使用 "encoding='latin1'"。

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

   如果 *buffers* 不为 "None"，则每次 pickle 流引用一个 带外的 缓冲区
   视图时，消耗的对象都应该是一个启用缓冲区的对象的可迭代对象。 这样的
   缓冲区将按顺序提供给 Pickler 对象的 *buffer_callback*。

   在 3.8 版本发生变更: 加入了 *buffers* 参数。

   load()

      从构造函数中指定的文件对象里读取封存好的对象，重建其中特定对象的
      层次结构并返回。封存对象以外的其他字节将被忽略。

   persistent_load(pid)

      默认抛出 "UnpicklingError" 异常。

      如果定义了此方法，"persistent_load()" 应当返回持久化 ID *pid* 所
      指定的对象。 如果遇到无效的持久化 ID，则应当引发
      "UnpicklingError"。

      参阅 持久化外部对象 获取详情和使用示例。

      在 3.13 版本发生变更: 在 "Unpickler" 的 C 实现中添加了此方法的默
      认实现。

   find_class(module, name)

      如有必要，导入 *module* 模块并返回其中名叫 *name* 的对象，其中
      *module* 和 *name* 参数都是 "str" 对象。注意，不要被这个函数的名
      字迷惑， "find_class()" 同样可以用来导入函数。

      子类可以重写此方法，来控制加载对象的类型和加载对象的方式，从而尽
      可能降低安全风险。参阅 限制全局变量 获取更详细的信息。

      引发一个 审计事件 "pickle.find_class" 并附带参数 "module",
      "name"。

class pickle.PickleBuffer(buffer)

   缓冲区的包装器 (wrapper)，缓冲区中包含着可封存的数据。*buffer* 必须
   是一个 buffer-providing 对象，比如 *bytes-like object* 或多维数组。

   "PickleBuffer" 本身就可以生成缓冲区对象，因此可以将其传递给需要缓冲
   区生成器的其他 API，比如 "memoryview"。

   "PickleBuffer" 对象只能用 pickle 版本 5 及以上协议进行序列化。它们
   符合 带外序列化 的条件。

   Added in version 3.8.

   raw()

      返回该缓冲区底层内存区域的 "memoryview"。 返回的对象是一维的、C
      连续布局的 memoryview，格式为 "B" (无符号字节)。 如果缓冲区既不
      是 C 连续布局也不是 Fortran 连续布局的，则抛出 "BufferError" 异
      常。

   release()

      释放由 PickleBuffer 占用的底层缓冲区。


可以被封存/解封的对象
=====================

下列类型可以被封存：

* 内置常量 ("None", "True", "False", "Ellipsis" 和 "NotImplemented")；

* 整数、浮点数、复数;

* 字符串、字节串、字节数组;

* 只包含可封存对象的元组、列表、集合和字典;

* 可在模块最高层级上访问的（内置与用户自定义的）函数（使用 "def"，而不
  是使用 "lambda" 定义）;

* 可在模块最高层级上访问的类;

* 这些类的实例对于调用 "__getstate__()" 的结果是可封存的（请参阅 封存
  类实例 一节了解详情）。

尝试封存不能被封存的对象会抛出 "PicklingError" 异常，异常发生时，可能
有部分字节已经被写入指定文件中。尝试封存递归层级很深的对象时，可能会超
出最大递归层级限制，此时会抛出 "RecursionError" 异常，可以通过
"sys.setrecursionlimit()" 调整递归层级，不过请谨慎使用这个函数，因为可
能会导致解释器崩溃。

请注意（内置与用户自定义的）函数是按完整 *qualified name*，而不是按值
来封存的。 [2]  这意味着只会封存函数名称，以及包含它的模块和类名称。
函数的代码，以及函数的属性都不会被封存。 因而定义它的模块在解封环境中
必须可以被导入，并且模块必须包含所命名的对象，否则将会引发异常。 [3]

类似地，类也是按完整限定名称来封存的，因此在解封环境中也会应用相同的限
制。 请注意类的代码或数据都不会被封存，因此在下面的示例中类属性 "attr"
不会在解封环境中被恢复:

   class Foo:
       attr = 'A class attribute'

   picklestring = pickle.dumps(Foo)

这些限制决定了为什么可封存的函数和类必须在一个模块的最高层级上定义。

类似的，在封存类的实例时，类的代码和数据不会随它们一起被封存，只有实际
数据会被封存。 这样设计有其目的，在将来修复类中的错误或给类增加方法之
后仍然可以载入较早版本创建的对象。 如果你打算长期使用某些可能有许多版
本的类的对象，那么在对象中设置一个版本号以便通过类的 "__setstate__()"
方法进行适当的转换就是值得做的事情。


封存类实例
==========

在本节中，我们描述了可用于定义、自定义和控制如何封存和解封类实例的通用
流程。

在大多数情况下，使一个实例可被封存不需要任何额外的代码。 根据默认设置
，pickle 将通过内省来获取实例的类及属性。 当一个类实例被解封时，它的
"__init__()" 方法通常 *不会* 被唤起。 默认的行为会先创建一个未初始化的
实例然后恢复已保存的属性。 下面的代码展示了这种行为的具体实现:

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

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

类可以改变默认行为，只需定义以下一种或几种特殊方法：

object.__getnewargs_ex__()

   In protocols 2 and newer, classes that implement the
   "__getnewargs_ex__()" method can dictate the values passed to the
   "__new__()" method upon unpickling.  The method must return a pair
   "(args, kwargs)" where *args* is a tuple of positional arguments
   and *kwargs* a dictionary of named arguments for constructing the
   object.  Those will be passed to the "__new__()" method upon
   unpickling.

   如果类的 "__new__()" 方法只接受关键字参数，则应当实现这个方法。否则
   ，为了兼容性，更推荐实现 "__getnewargs__()" 方法。

   在 3.6 版本发生变更: "__getnewargs_ex__()" 现在可用于第 2 和第 3 版
   协议。

object.__getnewargs__()

   这个方法与上一个 "__getnewargs_ex__()" 方法类似，但仅支持位置参数。
   它要求返回一个 tuple 类型的 "args"，用于解封时传递给 "__new__()" 方
   法。

   如果定义了 "__getnewargs_ex__()"，那么 "__getnewargs__()" 就不会被
   调用。

   在 3.6 版本发生变更: 在 Python 3.6 前，第 2、3 版协议会调用
   "__getnewargs__()"，更高版本协议会调用 "__getnewargs_ex__()"。

object.__getstate__()

   类还可以通过重写方法 "__getstate__()" 来进一步影响它们的实例要如何
   被封存。 该方法将被调用并且其返回的对象会被当作实例的内容来封存，而
   不是使用默认状态。 这有几种情况:

   * 对于没有实例 "__dict__" 以及没有 "__slots__" 的类，默认状态为
     "None"。

   * 对于具有实例 "__dict__" 而没有 "__slots__" 的类，默认状态为
     "self.__dict__"。

   * 对于具有实例 "__dict__" 和 "__slots__" 的类，默认状态为一个由两个
     字典:  "self.__dict__"、以及将槽位名称映射到槽位值的字典所组成的
     元组。 只有包含具体值的槽位才会被包括在后一个字典当中。

   * 对于具有 "__slots__" 而没有实例 "__dict__" 的类，默认状态为一个第
     一项是 "None" 而第二项是上述将槽位名称映射到槽位值的字典的元组。

   在 3.11 版本发生变更: 将 "__getstate__()" 方法的默认实现添加到
   "object" 类中。

object.__setstate__(state)

   当解封时，如果类定义了 "__setstate__()"，就会在已解封状态下调用它。
   此时不要求实例的 state 对象必须是 dict。没有定义此方法的话，先前封
   存的 state 对象必须是 dict，且该 dict 内容会在解封时赋给新实例的
   __dict__。

   备注:

     如果 "__reduce__()" 在封存时返回一个 "None" 值状态，那么在解封时
     将不会调用 "__setstate__()" 方法。

请参阅 处理有状态的对象 一节了解如何使用 "__getstate__()" 和
"__setstate__()" 方法的更多信息。

备注:

  在解封时，某些方法比如 "__getattr__()", "__getattribute__()" 或
  "__setattr__()" 可能会在实例上被调用。 对于这些方法依赖于某些内部的
  不变量为真值的情况，类型应当实现 "__new__()" 以建立这样的不变量，因
  为当解封一个实例时 "__init__()" 并不会被调用。

正如我们会看到的，pickle 并不会直接使用上述的方法。 实际上，这些方法是
拷贝协议的一部分，它实现了 "__reduce__()" 特殊方法。 拷贝协议提供了统
一的接口用于在封存和拷贝对象时获取所需的数据。 [4]

在你的类中直接实现 "__reduce__()" 虽然功能很强但也容易出错。 因此，类
的设计者应当尽可能使用高层级的接口 (即 "__getnewargs_ex__()",
"__getstate__()" 和 "__setstate__()")。 不过，我们仍然会演示使用
"__reduce__()" 是唯一选项或是更高效的封存或是两者兼有的场景。

object.__reduce__()

   该接口当前定义如下。"__reduce__()" 方法不带任何参数，并且应返回字符
   串或最好返回一个元组（返回的对象通常称为“reduce 值”）。

   If a string is returned, the string should be interpreted as the
   name of a global variable.  It should be the object's local name
   relative to its module; the pickle module searches the module
   namespace to determine the object's module: for a given "obj" to be
   pickled, the "__module__" attribute is looked up on "obj" directly,
   which falls back to a lookup on the type of "obj" if no
   "__module__" instance attribute is set. This behaviour is typically
   useful for singletons.

   如果返回的是元组，则应当包含 2 到 6 个元素，可选元素可以省略或设置
   为 "None"。每个元素代表的意义如下：

   * 一个可调用对象，该对象会在创建对象的最初版本时调用。

   * 可调用对象的参数，是一个元组。如果可调用对象不接受参数，必须提供
     一个空元组。

   * 可选元素，用于表示对象的状态，将被传给前述的 "__setstate__()" 方
     法。 如果对象没有此方法，则这个元素必须是字典类型，并会被添加至
     "__dict__" 属性中。

   * 可选项，一个返回连续条目的迭代器（而不是序列）。 这些条目将使用
     "obj.append(item)" 或是使用 "obj.extend(list_of_items)" 批量地添
     加到对象中。 这主要用于列表的子类，但也可以用于其他类，只要它们具
     有使用相应签名的 "append()" 和 "extend()" 方法。 （具体是使用
     "append()" 还是 "extend()" 取决于所使用的 pickle 协议版本以及要插
     入的条目数量，所以这两个方法都必须被支持。）

   * 可选元素，一个返回连续键值对的迭代器（而不是序列）。这些键值对将
     会以 "obj[key] = value" 的方式存储于对象中。该元素主要用于 dict
     子类，也可以用于那些实现了 "__setitem__()" 的类。

   * 可选元素，一个带有 "(obj, state)" 签名的可调用对象。该可调用对象
     允许用户以编程方式控制特定对象的状态更新行为，而不是使用 "obj" 的
     静态 "__setstate__()" 方法。如果此处不是 "None"，则此可调用对象的
     优先级高于 "obj" 的 "__setstate__()"。

     Added in version 3.8: 新增了元组的第 6 项，可选元素 "(obj,
     state)"。

object.__reduce_ex__(protocol)

   作为替代选项，也可以实现 "__reduce_ex__()" 方法。 此方法的唯一不同
   之处在于它应接受一个整型参数用于指定协议版本。 如果定义了这个函数，
   则会覆盖 "__reduce__()" 的行为。 此外，"__reduce__()" 方法会自动成
   为扩展版方法的同义词。 这个函数主要用于为以前的 Python 版本提供向后
   兼容的 reduce 值。


持久化外部对象
--------------

For the benefit of object persistence, the "pickle" module supports
the notion of a reference to an object outside the pickled data
stream.  Such objects are referenced by a persistent ID, which should
be either a string of alphanumeric characters (for protocol 0) [5] or
just an arbitrary object (for any newer protocol).

The resolution of such persistent IDs is not defined by the "pickle"
module; it will delegate this resolution to the user-defined methods
on the pickler and unpickler, "persistent_id()" and
"persistent_load()" respectively.

要通过持久化 ID 将外部对象封存，必须在 pickler 中实现
"persistent_id()" 方法，该方法接受需要被封存的对象作为参数，返回一个
"None" 或返回该对象的持久化 ID。如果返回 "None"，该对象会被按照默认方
式封存为数据流。如果返回字符串形式的持久化 ID，则会封存这个字符串并加
上一个标记，这样 unpickler 才能将其识别为持久化 ID。

要解封外部对象，Unpickler 必须实现 "persistent_load()" 方法，接受一个
持久化 ID 对象作为参数并返回一个引用的对象。

下面是一个全面的例子，展示了如何使用持久化 ID 来封存外部对象。

   # 介绍如何使用持久性 ID 基于引用
   # 对外部对象进行 pickle 的简单示例。

   import pickle
   import sqlite3
   from collections import namedtuple

   # 代表数据库中一条记录的简单类。
   MemoRecord = namedtuple("MemoRecord", "key, task")

   class DBPickler(pickle.Pickler):

       def persistent_id(self, obj):
           # 我们不是将 MemoRecord 作为常规类实例进行 pickle，
           # 而是发出一个持久性 ID。
           if isinstance(obj, MemoRecord):
               # 这里，我们的持久性 ID 就是一个元组，包含标签和键，
               # 它指向数据库中的特定记录。
               return ("MemoRecord", obj.key)
           else:
               # 如果 obj 没有持久性 ID，则返回 None。 这意味着 obj
               # 需要被正常地 pickle。
               return None


   class DBUnpickler(pickle.Unpickler):

       def __init__(self, file, connection):
           super().__init__(file)
           self.connection = connection

       def persistent_load(self, pid):
           # 此方法会在遇到一个持久性 ID 时被唤起。
           # 在这里，pid 是 DBPickler 所返回的元组。
           cursor = self.connection.cursor()
           type_tag, key_id = pid
           if type_tag == "MemoRecord":
               # 从数据库提取被引用的记录并将其返回。
               cursor.execute("SELECT * FROM memos WHERE key=?", (str(key_id),))
               key, task = cursor.fetchone()
               return MemoRecord(key, task)
           else:
               # 如果你无法返回正确的对象则总是引发一个错误。
               # 否则，反 pickle 操作将认为持久性 ID 所引用的对象
               # 为 None。
               raise pickle.UnpicklingError("unsupported persistent object")


   def main():
       import io
       import pprint

       # 初始化并填充我们的数据库。
       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,))

       # 提取要进行 pickle 的记录。
       cursor.execute("SELECT * FROM memos")
       memos = [MemoRecord(key, task) for key, task in cursor]
       # 使用我们自定义的 DBPickler 保存记录。
       file = io.BytesIO()
       DBPickler(file).dump(memos)

       print("Pickled records:")
       pprint.pprint(memos)

       # 更新一条记录，以确保有效。
       cursor.execute("UPDATE memos SET task='learn italian' WHERE key=1")

       # 从 pickle 数据流加载记录。
       file.seek(0)
       memos = DBUnpickler(file, conn).load()

       print("Unpickled records:")
       pprint.pprint(memos)


   if __name__ == '__main__':
       main()


Dispatch 表
-----------

如果想对某些类进行自定义封存，而又不想在类中增加用于封存的代码，就可以
创建带有特殊 dispatch 表的 pickler。

"copyreg" 模块所管理的全局 dispatch 表可通过 "copyreg.dispatch_table"
来访问。 因此，可以选择使用经过修改的 "copyreg.dispatch_table" 副本作
为私有 dispatch 表。

例如

   f = io.BytesIO()
   p = pickle.Pickler(f)
   p.dispatch_table = copyreg.dispatch_table.copy()
   p.dispatch_table[SomeClass] = reduce_SomeClass

创建了一个带有自有 dispatch 表的 "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.pickle(SomeClass, reduce_SomeClass)
   f = io.BytesIO()
   p = pickle.Pickler(f)

会修改由 "copyreg" 模块的所有用户共享的全局分发表。


处理有状态的对象
----------------

下面的例子展示了如何修改类的封存行为。 下面的 "TextReader" 类会打开一
个文本文件，每次调用其 "readline()" 方法时将返回行号和该行的内容。 如
果一个 "TextReader" 实例被封存，则 *除了* 文件对象以外的所有属性都会被
保存。 当实际被解封时，该文件将被重新打开，并从最后的位置开始恢复读取
。 实现此行为需要使用 "__setstate__()" 和 "__getstate__()" 方法。

   class TextReader:
       """打印文本文件的行内容和行号。"""

       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):
           # 从 self.__dict__ 拷贝对象的状态，其中包含
           # 所有的实例属性。 总是使用 dict.copy() 方法
           # 以避免修改原始状态。
           state = self.__dict__.copy()
           # 移除不可 pickle 的条目。
           del state['file']
           return state

       def __setstate__(self, state):
           # 恢复实例属性 (即 filename 和 lineno)。
           self.__dict__.update(state)
           # 恢复之前所打开文件的状态。 为做到这点，我们需要
           # 重新打开它并从其中读取直到恢复行计数。
           file = open(self.filename)
           for _ in range(self.lineno):
               file.readline()
           # 最后，保存该文件。
           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!'


类型，函数和其他对象的自定义归约
================================

Added in version 3.8.

有时，"dispatch_table" 可能不够灵活。 特别是当我们想要基于对象类型以外
的其他规则来对封存进行定制，或是当我们想要对函数和类的封存进行定制的时
候。

对于那些情况，可以子类化 "Pickler" 类并实现 "reducer_override()" 方法
。 此方法可返回任意 reduction 元组 (参见 "__reduce__()")。 它也可以选
择返回 "NotImplemented" 以回退至传统的行为。

如果同时定义了 "dispatch_table" 和 "reducer_override()"，则
"reducer_override()" 方法具有优先权。

备注:

  出于性能理由，可能不会为以下对象调用 "reducer_override()": "None",
  "True", "False", 以及 "int", "float", "bytes", "str", "dict", "set",
  "frozenset", "list" 和 "tuple" 的具体实例。

以下是一个简单的例子，其中我们允许封存并重新构建一个给定的类:

   import io
   import pickle

   class MyClass:
       my_attribute = 1

   class MyPickler(pickle.Pickler):
       def reducer_override(self, obj):
           """针对 MyClass 的自定义缩减器。"""
           if getattr(obj, "__name__", None) == "MyClass":
               return type, (obj.__name__, obj.__bases__,
                             {'my_attribute': obj.my_attribute})
           else:
               # 对于任何其他对象，回退为正常缩减操作
               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


外部缓冲区
==========

Added in version 3.8.

In some contexts, the "pickle" module is used to transfer massive
amounts of data.  Therefore, it can be important to minimize the
number of memory copies, to preserve performance and resource
consumption.  However, normal operation of the "pickle" module, as it
transforms a graph-like structure of objects into a sequential stream
of bytes, intrinsically involves copying data to and from the pickle
stream.

如果 *provider* (待传输对象类型的实现) 和 *consumer* (通信系统的实现)
都支持 pickle 第 5 版或更高版本所提供的外部传输功能，则此约束可以被撤
销。


提供方 API
----------

需要 pickle 的大型数据对象必须实现专门用于协议 5 以上版本的
"__reduce_ex__()" 方法，该方法将为任何大型数据返回一个 "PickleBuffer"
实例（而不是 "bytes" 对象）。

A "PickleBuffer" object *signals* that the underlying buffer is
eligible for out-of-band data transfer.  Those objects remain
compatible with normal usage of the "pickle" module.  However,
consumers can also opt-in to tell "pickle" that they will handle those
buffers by themselves.


使用方 API
----------

当序列化一个对象图时，通信系统可以启用对所生成 "PickleBuffer" 对象的定
制处理。

发送端需要传递 *buffer_callback* 参数到 "Pickler" (或是到 "dump()" 或
"dumps()" 函数)，该回调函数将在封存对象图时附带每个所生成的
"PickleBuffer" 被调用。 由 *buffer_callback* 所累积的缓冲区的数据将不
会被拷贝到 pickle 流，而是仅插入一个简单的标记。

接收端需要传递 *buffers* 参数到 "Unpickler" (或是到 "load()" 或
"loads()" 函数)，其值是一个由缓冲区组成的可迭代对象，它会被传递给
*buffer_callback*。 该可迭代对象应当按其被传递给 *buffer_callback* 时
的顺序产生缓冲区。 这些缓冲区将提供对象重构造器所期望的数据，对这些数
据的封存产生了原本的 "PickleBuffer" 对象。

在发送端和接受端之间，通信系统可以自由地实现它自己用于外部缓冲区的传输
机制。 潜在的优化包括使用共享内存或基于特定数据类型的压缩等。


示例
----

下面是一个小例子，在其中我们实现了一个 "bytearray" 的子类，能够用于外
部缓冲区封存:

   class ZeroCopyByteArray(bytearray):

       def __reduce_ex__(self, protocol):
           if protocol >= 5:
               return type(self)._reconstruct, (PickleBuffer(self),), None
           else:
               # PickleBuffer 当 pickle 协议 <= 4 时是禁用的。
               return type(self)._reconstruct, (bytearray(self),)

       @classmethod
       def _reconstruct(cls, obj):
           with memoryview(obj) as m:
               # 获取原始缓冲区对象的句柄
               obj = m.obj
               if type(obj) is cls:
                   # 原始缓冲区对象是一个 ZeroCopyByteArray，
                   # 则将其原样返回。
                   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: 生成了副本

但是如果我们传入 *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: 没有生成副本

这个例子受限于 "bytearray" 会自行分配内存这一事实：你无法基于另一个对
象的内存创建 "bytearray" 的实例。 但是，第三方数据类型例如 NumPy 数组
则没有这种限制，允许在单独进程或系统间传输时使用零拷贝的封存（或是尽可
能少地拷贝） 。

参见: **PEP 574** -- 带有外部数据缓冲区的 pickle 协议 5


限制全局变量
============

默认情况下，解封将会导入在 pickle 数据中找到的任何类或函数。 对于许多
应用来说，此行为是不可接受的，因为它会允许解封器导入并唤起任意代码。
只须考虑当这个手工构建的 pickle 数据流被加载时会做什么:

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

在这个例子里，解封器导入 "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):
           # 只允许来自 builtins 的安全的类。
           if module == "builtins" and name in safe_builtins:
               return getattr(builtins, name)
           # 禁止任何其他的类。
           raise pickle.UnpicklingError("global '%s.%s' is forbidden" %
                                        (module, name))

   def restricted_loads(s):
       """类似于 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" 中的编组 API 或是
第三方解决方案。


性能
====

Recent versions of the pickle protocol (from protocol 2 and upwards)
feature efficient binary encodings for several common features and
built-in types. Also, the "pickle" module has a transparent optimizer
written in C.


例子
====

对于最简单的代码，请使用 "dump()" 和 "load()" 函数。

   import pickle

   # pickle 所支持的任意对象集。
   data = {
       'a': [1, 2.0, 3+4j],
       'b': ("character string", b"byte string"),
       'c': {None, True, False}
   }

   with open('data.pickle', 'wb') as f:
       # 使用最高版本可用协议对 'data' 字典进行 pickle。
       pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)

以下示例读取之前封存的数据。

   import pickle

   with open('data.pickle', 'rb') as f:
       # 将自动检测所使用的协议版本，因此我们
       # 不需要指定它。
       data = pickle.load(f)


命令行接口
==========

"pickle" 模块可以作为脚本从命令行唤起，它将显示 pickle 文件的内容。 不
过，当你想要检查的 pickle 文件来自不受信任的来源时，"-m pickletools"
是更安全的选择因为它不会执行 pickle 字节码，请参阅 pickletools CLI 用
法。

   python -m pickle pickle_file [pickle_file ...]

可以接受以下选项：

pickle_file

   一个要读取的 pickle 文件，或者为 "-" 表示从标准输入读取。

参见:

  模块 "copyreg"
     为扩展类型提供 pickle 接口所需的构造函数。

  模块 "pickletools"
     用于处理和分析已封存数据的工具。

  模块 "shelve"
     带索引的对象数据库；使用 "pickle"。

  模块 "copy"
     浅层 (shallow) 和深层 (deep) 复制对象操作

  模块 "marshal"
     高效地序列化内置类型的数据。

-[ 备注 ]-

[1] 不要把它与 "marshal" 模块混淆。

[2] 这就是为什么 "lambda" 函数不可被封存：所有 "lambda" 函数都共享同一
    个名字:  "<lambda>"。

[3] 抛出的异常有可能是 "ImportError" 或 "AttributeError"，也可能是其他
    异常。

[4] "copy" 模块使用这一协议实现浅层 (shallow) 和深层 (deep) 复制操作。

[5] 对于字符数字类字符的限制是由于持久化 ID 在协议版本 0 中是由分行符
    来分隔的。 因此如果持久化 ID 中出现了任何形式的分行符，封存结果就
    将变得无法读取。
