Skip to main content

12.1. pickle — Python对象序列化

源代码: Lib/pickle.py


pickle 模块实现用于对Python对象结构进行序列化和反序列化的二进制协议。 “Pickling” 是一个将Python对象层次结构转换为字节流的过程,“unpickling” 是反向操作,借此字节流(来自 binary filebytes-like object)被转换回对象层次结构。酸洗(和解胶)也称为“串联”,“编组”,[1] 或“扁平化”;然而,为了避免混淆,这里使用的术语是“酸洗”和“解冻”。

警告

pickle 模块对于错误或恶意构造的数据是不安全的。切勿取消从不受信任或未经身份验证的来源接收的数据。

12.1.1. 与其他Python模块的关系

12.1.1.1. 与 marshal 的比较

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

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

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

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

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

  • marshal 序列化格式不能保证在Python版本之间可移植。因为它在生活中的主要工作是支持 .pyc 文件,所以Python实现者保留在需要时以非向后兼容的方式改变序列化格式的权利。 pickle 序列化格式保证在Python版本之间向后兼容。

12.1.1.2. 与 json 的比较

在酸洗方案和 JSON(JavaScript对象表示法) 之间有基本的区别:

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

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

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

  • 默认情况下,JSON只能表示Python内置类型的一个子集,并且没有自定义类; pickle可以代表极其庞大的Python类型(其中很多是通过巧妙地使用Python的内省功能自动实现的;复杂的情况可以通过实现 特定对象API 来解决)。

参见

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

12.1.2. 数据流格式

pickle 使用的数据格式是特定于Python的。这具有的优点是没有由诸如JSON或XDR(其不能表示指针共享)的外部标准强加的限制;然而这意味着非Python程序可能无法重建pickled Python对象。

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

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

目前有5种不同的协议可以用于酸洗。使用的协议越高,更新的Python版本需要读取生产的泡菜。

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

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

  • 协议版本2在Python 2.3中引入。它提供了更高效的酸洗 new-style class。有关协议2带来的改进的信息,请参阅 PEP 307

  • 在Python 3.0中添加了协议版本3。它显式支持 bytes 对象,不能被Python 2.x取消绑定。这是默认协议,当需要与其他Python 3版本兼容时,推荐使用协议。

  • 在Python 3.4中添加了协议版本4。它增加了对非常大的对象,pickling更多种类的对象和一些数据格式优化的支持。有关协议4带来的改进的信息,请参阅 PEP 3154

注解

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

12.1.3. 模块接口

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

pickle 模块提供以下常量:

pickle.HIGHEST_PROTOCOL

整数,最高的 协议版本 可用。该值可以作为 protocol 值传递给函数 dump()dumps() 以及 Pickler 构造函数。

pickle.DEFAULT_PROTOCOL

一个整数,默认的 协议版本 用于pickling。可能小于 HIGHEST_PROTOCOL。目前默认的协议是3,一个为Python 3设计的新协议。

pickle 模块提供以下功能,使酸洗过程更方便:

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

obj 的腌制表示写入开放 file object file。这相当于 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中使用的旧模块名称,以便使用Python 2可读取pickle数据流。

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

将对象的经过腌制的表示作为 bytes 对象返回,而不是将其写入文件。

参数 protocolfix_imports 具有与 dump() 中相同的含义。

pickle.load(file, *, fix_imports=True, encoding="ASCII", errors="strict")

从打开的 file object file 读取一个腌制对象表示,并返回其中指定的重构对象层次结构。这相当于 Unpickler(file).load()

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

参数 file 必须有两个方法,一个读取整数参数的read()方法和一个不需要参数的readline()方法。两种方法都应该返回字节。因此,file 可以是为二进制读取打开的磁盘文件,io.BytesIO 对象或满足此接口的任何其他自定义对象。

可选的关键字参数是 fix_importsencodingerrors,用于控制Python 2生成的pickle流的兼容性支持。如果 fix_imports 为true,pickle将尝试将旧的Python 2名称映射到Python 3中使用的新名称。 encodingerrors 告诉pickle如何解码由Python 2 pickled的8位字符串实例;这些默认分别为’ASCII’和’strict’。 encoding 可以“字节”来读取这些8位字符串实例作为字节对象。

pickle.loads(bytes_object, *, fix_imports=True, encoding="ASCII", errors="strict")

bytes 对象读取腌制对象层次结构,并返回其中指定的重构对象层次结构。

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

可选的关键字参数是 fix_importsencodingerrors,用于控制Python 2生成的pickle流的兼容性支持。如果 fix_imports 为true,pickle将尝试将旧的Python 2名称映射到Python 3中使用的新名称。 encodingerrors 告诉pickle如何解码由Python 2 pickled的8位字符串实例;这些默认分别为’ASCII’和’strict’。 encoding 可以“字节”来读取这些8位字符串实例作为字节对象。

pickle 模块定义了三个例外:

exception pickle.PickleError

其他pickling异常的公共基类。它继承 Exception

exception pickle.PicklingError

Pickler 遇到不可拆分对象时引发错误。它继承 PickleError

参考 什么可以酸洗和取消? 了解什么类型的对象可以酸洗。

exception pickle.UnpicklingError

在解除对象的取消(例如数据损坏或安全违规)时出现问题。它继承 PickleError

注意,在解压缩期间也可能引发其他异常,包括(但不一定限于)AttributeError,EOFError,ImportError和IndexError。

pickle 模块导出两个类,PicklerUnpickler

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中使用的旧模块名称,以便使用Python 2可读取pickle数据流。

dump(obj)

obj 的腌制表示写入构造函数中给出的打开文件对象。

persistent_id(obj)

默认情况下不执行任何操作。这存在,所以一个子类可以覆盖它。

如果 persistent_id() 返回 Noneobj 像往常一样被腌制。任何其他值都会导致 Pickler 将返回的值作为 obj 的持久性标识。此持久性ID的含义应由 Unpickler.persistent_load() 定义。请注意,persistent_id() 返回的值本身不能有持久的ID。

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

dispatch_table

pickler对象的调度表是可以使用 copyreg.pickle() 声明的 减少功能 的注册表。它是一个映射,其键是类并且其值是缩减函数。 reduce函数接受关联类的单个参数,并且应该遵循与 __reduce__() 方法相同的接口。

默认情况下,pickler对象将不具有 dispatch_table 属性,而是将使用由 copyreg 模块管理的全局分派表。但是,要自定义特定pickler对象的pickling,可以将 dispatch_table 属性设置为类似dict的对象。或者,如果 Pickler 的子类具有 dispatch_table 属性,则这将被用作该类的实例的默认分派表。

有关使用示例,请参阅 调度表

3.3 新版功能.

fast

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

如果您需要更多的小型泡菜,请使用 pickletools.optimize()

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

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

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

参数 file 必须有两个方法,一个读取整数参数的read()方法和一个不需要参数的readline()方法。两种方法都应该返回字节。因此,file 可以是为二进制读取而打开的磁盘文件对象,io.BytesIO 对象或满足该接口的任何其他自定义对象。

可选的关键字参数是 fix_importsencodingerrors,用于控制Python 2生成的pickle流的兼容性支持。如果 fix_imports 为true,pickle将尝试将旧的Python 2名称映射到Python 3中使用的新名称。 encodingerrors 告诉pickle如何解码由Python 2 pickled的8位字符串实例;这些默认分别为’ASCII’和’strict’。 encoding 可以“字节”来读这些ß8位字符串实例作为字节对象。

load()

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

persistent_load(pid)

默认情况下提升 UnpicklingError

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

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

find_class(module, name)

如果需要,导入 module,并从中返回名为 name 的对象,其中 modulename 参数是 str 对象。注意,不像它的名字所暗示的,find_class() 也用于查找功能。

子类可以覆盖此类,以获得对什么类型的对象以及如何加载它们的控制,从而潜在地降低安全风险。有关详细信息,请参阅 限制全局

12.1.4. 什么可以酸洗和取消?

以下类型可以选择:

  • NoneTrueFalse

  • 整数,浮点数,复数

  • 字符串,字节,字节数

  • 元组,列表,集合和仅包含可拾取对象的字典

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

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

  • 在模块的顶层定义的类

  • 这样的类的实例,其 __dict__ 或调用 __getstate__() 的结果是可选的(参见 Pickling类实例 部分的细节)。

尝试挑选不可拆分对象将引发 PicklingError 异常;当发生这种情况时,未指定数量的字节可能已经被写入底层文件。试图挑选高度递归的数据结构可能会超过最大递归深度,在这种情况下会产生 RecursionError。您可以使用 sys.setrecursionlimit() 小心提高此限制。

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

类似地,类通过命名引用进行选择,因此在取消取消环境中应用相同的限制。注意,没有类的代码或数据被pickled,因此在下面的示例中,类属性 attr 在unpickling环境中不恢复:

class Foo:
    attr = 'A class attribute'

picklestring = pickle.dumps(Foo)

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

类似地,当类实例被pickled时,它们的类的代码和数据不与它们一起被腌制。只有实例数据被酸洗。这是有意的,所以你可以修复类中的错误或添加方法到类,并仍然加载的对象,使用早期版本的类创建的。如果你计划有长期存在的对象将看到一个类的许多版本,可能值得把一个版本号在对象中,以便可以通过类的 __setstate__() 方法进行适当的转换。

12.1.5. Pickling类实例

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

在大多数情况下,不需要额外的代码来使实例可拾取。默认情况下,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 是位置参数的元组,kwargs 是用于构造对象的命名参数的字典。这些将在解压后传递到 __new__() 方法。

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

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

object.__getnewargs__()

此方法的作用与 __getnewargs_ex__() 类似,但仅支持位置参数。它必须返回一个参数 args 的元组,它将在取消绑定时传递给 __new__() 方法。

如果定义了 __getnewargs_ex__(),则不会调用 __getnewargs__()

在 3.6 版更改: 在Python 3.6之前,在协议2和3中调用 __getnewargs__() 而不是 __getnewargs_ex__()

object.__getstate__()

类可以进一步影响他们的实例如何被酸洗;如果类定义了方法 __getstate__(),它被调用,并且返回的对象被作为实例的内容而不是实例的字典的内容。如果 __getstate__() 方法不存在,则实例的 __dict__ 像往常一样被腌制。

object.__setstate__(state)

取消绑定后,如果类定义了 __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__() 方法不使用参数,并且应返回字符串或优选的元组(返回的对象通常被称为“减少值”)。

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

当返回一个元组时,它必须在两到五个项之间。可以省略可选项目,或者可以提供 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__() 方法。唯一的区别是这个方法应该采用单个整数参数,即协议版本。当定义时,酸菜将优先于 __reduce__() 方法。此外,__reduce__() 自动成为扩展版本的同义词。此方法的主要用途是为较早的Python版本提供向后兼容的reduce值。

12.1.5.1. 外部对象的持久性

为了对象持久化的益处,pickle 模块支持对经过腌制的数据流外部的对象的引用的概念。这些对象由永久ID引用,持久ID应该是字母数字字符串(对于协议0) [5] 或只是任意对象(对于任何较新的协议)。

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

要拣选具有外部持久性标识的对象,pickler必须有一个自定义 persistent_id() 方法,它将一个对象作为参数,并返回该对象的 None 或持久性标识。当 None 返回时,pickler只是像正常的pickle对象。当返回持久性ID字符串时,pickler将拾取该对象以及一个标记,以便unpickler将它识别为持久性ID。

要取消取消外部对象,unpickler必须有一个自定义 persistent_load() 方法,它接受一个持久的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()

12.1.5.2. 调度表

如果想要定制一些类的pickling而不打扰任何依赖于pickling的代码,那么可以创建一个带有私有调度表的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

创建一个具有专用处理 SomeClass 类的专用分派表的 pickle.Pickler 的实例。或者,代码

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 实例被酸洗,则保存文件对象成员的所有属性 except。当实例取消取消时,文件将重新打开,并从最后一个位置继续读取。 __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.7. 性能

最新版本的pickle协议(从协议2及以上版本)为几个常见功能和内置类型提供高效的二进制编码。此外,pickle 模块具有以C编写的透明优化程序。

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)

以下示例读取生成的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 功能不能被腌制:所有 lambda 功能共享相同的名称:<lambda>

[3]

提出的例外可能是 ImportErrorAttributeError,但它可能是其他的。

[4]

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

[5]

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