12.1. pickle - Python对象序列化

源代码: Lib / pickle.py

pickle模块实现了用于对Python对象结构进行序列化和反序列化的二进制协议。“Pickling”是将Python对象转换为字节流的过程,“unpickling”是反向操作,由此字节流二进制文件字节对象)转换回对象结构。酸洗(和解胶)也称为“串联”,“编组”,[1]或“压平”;然而,为了避免混淆,这里使用的术语是“pickling”和“unpickling”。

警告

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的比较

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

  • JSON是一种文本序列化格式(它输出unicode文本,虽然大多数时候它被编码为utf-8),而pickle是一个二进制序列化格式;
  • JSON是人类可读的,而pickle不是;
  • JSON是可互操作的,并且在Python生态系统之外广泛使用,而pickle是特定于Python的;
  • 默认情况下,JSON只能表示Python内建类型的一个子集,并且没有自定义类; pickle可以代表极大数量的Python类型(其中许多是通过巧妙地使用Python内省功能自动实现的;复杂的情况可以通过实现specific object APIs来解决)。

也可以看看

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

12.1.2.数据流格式

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

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

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

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

  • 协议版本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 version可用。此值可作为协议值传递给函数dump()dumps()以及Pickler

pickle.DEFAULT_PROTOCOL

整数,默认的protocol version用于酸洗。可能小于HIGHEST_PROTOCOL目前默认的协议是3,一个为Python 3设计的新协议。

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

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

obj的腌制表示写入打开的file object 文件这相当于Pickler(文件, 协议).dump(obj)

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

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

如果fix_imports为true且protocol小于3,pickle将尝试将新的Python 3名称映射到Python 2中使用的旧模块名称,以便pickle数据流可以用Python 2读取。

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

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

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

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

从打开的文件对象file读取pickled对象表示形式,并返回其中重新构建的对象层次结构。它等同于Unpickler(file).load()

Pickle协议的版本会自动检测,所以不需要protocol参数。超过腌制对象表示的字节将被忽略。

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

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

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

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

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

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

pickle模块定义了三个异常:

exception pickle.PickleError

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

exception pickle.PicklingError

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

请参阅What can be pickled and unpickled?以了解什么类型的对象可以腌制。

exception pickle.UnpicklingError

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

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

pickle模块导出两个类,PicklerUnpickler

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

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

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

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

如果fix_imports为true且protocol小于3,pickle将尝试将新的Python 3名称映射到Python 2中使用的旧模块名称,以便pickle数据流可以用Python 2读取。

dump(obj)

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

persistent_id(obj)

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

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

有关使用的详细信息和示例,请参见Persistence of External Objects

dispatch_table

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

By default, a pickler object will not have a dispatch_table attribute, and it will instead use the global dispatch table managed by the copyreg module. 但是,要自定义特定pickler对象的pickling,可以将dispatch_table属性设置为类似dict的对象。或者,如果Pickler的子类具有dispatch_table属性,那么这将用作该类的实例的默认分派表。

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

版本3.3中的新功能。

fast

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

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

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

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

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

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

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

load()

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

persistent_load(pid)

默认情况下引发UnpicklingError

如果已定义,persistent_load()应返回持久性标识pid指定的对象。如果遇到无效的持久性ID,应提出UnpicklingError

有关使用的详细信息和示例,请参见Persistence of External Objects

find_class(module, name)

如有必要,请输入模块,并返回名称的对象,其中模块名称参数str对象。注意,与其名称不同,find_class()也用于查找函数。

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

12.1.4.什么可以pickled和unpickled?

以下类型可以选择:

  • None, True, and False
  • 整数,浮点数,复数
  • 字符串,字节,字节数
  • 元组,列表,集合和仅包含可拾取对象的字典
  • 在模块顶层定义的函数(使用def,而不是lambda
  • 内建函数定义在模块的顶层
  • 在模块的顶层定义的类
  • 其类__dict__或调用__getstate__()的类的实例是可选的(有关详细信息,请参阅Pickling Class Instances一节)。

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

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

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

class Foo:
    attr = 'A class attribute'

picklestring = pickle.dumps(Foo)

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

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

12.1.5.pickling类实例

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

在大多数情况下,不需要额外的代码来使实例可拾取。默认情况下,pickle将通过内省检索实例的类和属性。当类实例取消取消时,其__init__()方法通常被调用。默认行为首先创建未初始化的实例,然后恢复保存的属性。以下代码显示了此行为的实现:

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

在协议4和更新版本中,实现__getnewargs_ex__()方法的类可以规定在取消发送时传递给__new__()方法的值。该方法必须返回一对(args, kwargs)其中args是位置参数的元组, kwargs用于构造对象的命名参数的字典。这些将在解压缩后传递给__new__()方法。

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

object.__getnewargs__()

此方法的作用与__getnewargs_ex__()类似,但适用于协议2和更高版本。它必须返回一个参数args的元组,它将在取消发送时传递给__new__()方法。

在协议4和更新版本中,如果定义__getnewargs_ex__(),则不会调用__getnewargs__()

object.__getstate__()

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

object.__setstate__(state)

取消绑定后,如果类定义__setstate__(),则以unpickled状态调用。在这种情况下,不需要状态对象是字典。否则,腌制状态必须是字典,其项目被分配给新实例的字典。

注意

如果__getstate__()返回一个假值,则在取消冻结时不会调用__setstate__()方法。

有关如何使用方法__getstate__()__setstate__()的详细信息,请参阅Handling Stateful Objects

注意

在取消时间,可以在实例上调用诸如__getattr__()__getattribute__()__setattr__()如果这些方法依赖于一些内部不变量为真,则类型应该实现__getnewargs__()__getnewargs_ex__()以建立这样的不变量;否则,将不会调用__new__()__init__()

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

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

object.__reduce__()

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

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

当返回一个元组时,它必须在两到五个项之间。可以省略可选项,也可以提供None作为其值。每个项目的语义是按顺序:

  • 将被调用以创建对象的初始版本的可调用对象。
  • 可调用对象的参数的元组。如果callable不接受任何参数,则必须给出一个空元组。
  • 可选地,对象的状态将被传递到对象的__setstate__()方法,如前所述。如果对象没有这样的方法,那么值必须是字典,并且它将被添加到对象的__dict__属性。
  • 可选地,迭代器(而不是序列)产生连续项。这些项将使用obj.append(item)或批量使用obj.extend(list_of_items)附加到对象。这主要用于列表子类,但可以由其他类使用,只要它们具有适当的声明的append()extend()方法。(是否使用append()extend()取决于使用的pickle协议版本以及要附加的项目数,因此必须支持两者。
  • 可选地,产生连续键值对的迭代器(而不是序列)。这些项将使用obj [key] = 存储到对象。这主要用于字典子类,但可以由其他类使用,只要它们实现__setitem__()
object.__reduce_ex__(protocol)

或者,可以定义__reduce_ex__()方法。唯一的区别是这个方法应该采用单个整数参数,即协议版本。当定义时,pickle将优先于__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只是像正常一样腌制对象。当返回持久性ID字符串时,pickler将拾取该对象以及一个标记,以便unpickler将它识别为持久性ID。

要取消取消外部对象,取消管理器必须有一个自定义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.Dispatch Tables

如果想要定制一些类的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实例被选中,除了文件对象成员的所有属性都被保存。当实例取消选中时,文件将重新打开,并从最后一个位置继续读取。__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
用于处理和分析pickled数据的工具。
模块shelve
索引的对象数据库;使用pickle
模块copy
浅和深对象复制。
模块marshal
内建类型的高性能序列化。

脚注

[1]不要将此与marshal模块混淆
[2]这就是为什么lambda函数不能被pickled:所有lambda函数共享同一个名称:<lambda>
[3]引发的异常可能是ImportErrorAttributeError,但可能是其他原因。
[4]copy模块使用此协议进行浅层和深层复制操作。
[5]对字母数字字符的限制是由于事实,协议0中的持久性ID由换行符字符分隔。因此,如果在持久性ID中出现任何类型的换行符,则生成的pickle将变得不可读。