You are here:  Home » Python » pickle-Python对象序列化(1)pickle和marshal模块永久存储Python数据(必读进阶Python教程)(参考资料)

pickle模块实现了用于序列化和反序列化Python对象结构的二进制协议。 Pickling是将Python对象层次结构转换为字节流的过程, unpickling是反向操作,从而将字节流(来自二进制文件类似字节的对象)转换回对象层次结构。酸洗(和去除)也可称为“序列化”,“编组”,[1]或“扁平化”; 但是,为了避免混淆,这里使用的术语是“酸洗”和“破坏”。

警告

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

与其他Python模块的关系

与比较marshal

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

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

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

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

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

  • 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的内省工具;复杂的案例可以通过实现特定的对象API来解决)。

也可以看看

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

数据流格式

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

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

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

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

  • 协议版本0是原始的“人类可读”协议,并且向后兼容早期版本的Python。
  • 协议版本1是旧的二进制格式,它也与早期版本的Python兼容。
  • 在Python 2.3中引入了协议版本2。它提供了更有效的新式类型的酸洗。参考PEP 307获取有关协议2带来的改进的信息。
  • 在Python 3.0中添加了协议版本3。它具有对bytes对象的显式支持, 并且不能被Python 2.x打开。这是默认协议,需要与其他Python 3版本兼容时的推荐协议。
  • 在Python 3.4中添加了协议版本4。它增加了对非常大的对象的支持,挑选更多种类的对象,以及一些数据格式优化。参考PEP 3154获取有关协议4带来的改进的信息。

注意

序列化是一种比持久性更原始的概念; 虽然 pickle读取和写入文件对象,但它不处理命名持久对象的问题,也不处理对持久对象的并发访问(甚至更复杂)的问题。该pickle模块可以将一个复杂的对象成字节流,它可以改造字节流成一个对象具有相同的内部结构。对这些字节流最明显的做法可能是将它们写入文件,但也可以想象它们通过网络发送或将它们存储在数据库中。该shelve 模块提供了一个简单的界面,用于在DBM样式的数据库文件上pickle和unpickle对象。

模块接口

要序列化对象层次结构,只需调用该dumps()函数即可。同样,要对数据流进行反序列化,请调用该loads()函数。但是,如果要对序列化和反序列化进行更多控制,可以分别创建一个Pickler或一个Unpickler对象。

pickle模块提供以下常量:

pickle.HIGHEST_PROTOCOL
整数, 可用的最高协议版本。这个值可以作为一个被传递协议的价值函数 dump()dumps()以及该Pickler 构造函数。
pickle.DEFAULT_PROTOCOL
整数,用于酸洗的默认协议版本。可能不到HIGHEST_PROTOCOL。目前,默认协议是3,这是为Python 3设计的新协议。

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

pickle.dumpobjfileprotocol = None*fix_imports = True 
obj的pickled表示写入打开的文件对象 文件。这相当于。Pickler(file, protocol).dump(obj)

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

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

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

pickle.dumpsobjprotocol = None*fix_imports = True 
将对象的pickled表示作为bytes对象返回,而不是将其写入文件。

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

pickle.loadfile*fix_imports = Trueencoding =“ASCII”errors =“strict” 
从打开的文件对象 文件中读取pickle对象表示,并返回其中指定的重构对象层次结构。这相当于Unpickler(file).load()

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

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

可选的关键字参数是fix_importsencodingerrors,用于控制Python 2生成的pickle流的兼容性支持。如果fix_imports为true,则pickle将尝试将旧的Python 2名称映射到Python 3中使用的新名称。编码和 错误告诉pickle如何解码Python 2腌制的8位字符串实例; 这些默认分别为’ASCII’和’strict’。该编码可以是“字节”作为字节对象读取这些8位串的实例。使用encoding='latin1'所需的取储存NumPy的阵列和实例datetimedate并且time被Python 2酸洗。

pickle.loadsbytes_object*fix_imports = Trueencoding =“ASCII”errors =“strict” 
bytes对象读取pickle对象层次结构并返回其中指定的重构对象层次结构。

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

可选的关键字参数是fix_importsencodingerrors,用于控制Python 2生成的pickle流的兼容性支持。如果fix_imports为true,则pickle将尝试将旧的Python 2名称映射到Python 3中使用的新名称。编码和 错误告诉pickle如何解码Python 2腌制的8位字符串实例; 这些默认分别为’ASCII’和’strict’。该编码可以是“字节”作为字节对象读取这些8位串的实例。使用encoding='latin1'所需的取储存NumPy的阵列和实例datetimedate并且time被Python 2酸洗。

pickle模块定义了三个例外:

异常pickle.PickleError
其他酸洗异常的通用基类。它继承了 Exception
异常pickle.PicklingError
遇到不可解析的对象时引发错误Pickler。它继承了PickleError

请参阅什么可以酸洗和去除?了解哪些物体可以腌制。

异常pickle.UnpicklingError
解开对象时出现问题(例如数据损坏或安全违规)时出错。它继承了PickleError

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

pickle模块导出两个类,Pickler并且 Unpickler

class pickle.Picklerfileprotocol = None*fix_imports = True 
这需要一个二进制文件来写一个pickle数据流。

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

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

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

dumpobj 
obj的pickled表示写入构造函数中给出的打开文件对象。
persistent_idobj 
默认情况下不做任何事 这是存在的,因此子类可以覆盖它。

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

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

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

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

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

版本3.3中的新功能。

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

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

class pickle.Unpicklerfile*fix_imports = Trueencoding =“ASCII”errors =“strict” 
这需要一个二进制文件来读取pickle数据流。

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

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

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

load
从构造函数中给出的打开文件对象中读取pickle对象表示,并返回其中指定的重构对象层次结构。超过pickle对象的表示的字节将被忽略。
persistent_loadpid 
UnpicklingError默认情况下提升。

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

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

find_class模块名称
必要时导入模块并从中返回名为name的对象,其中modulename参数是str对象。注意,与其名称不同,find_class()它也用于查找功能。

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

什么可以腌制和去除?

可以腌制以下类型:

  • NoneTrueFalse
  • 整数,浮点数,复数
  • 字符串,字节,字节数组
  • 仅包含可选对象的元组,列表,集和词典
  • 在模块的顶层定义的函数(使用def,不是 lambda
  • 在模块顶层定义的内置函数
  • 在模块顶层定义的类
  • 此类的实例__dict__或其调用结果__getstate__()是可选择的(有关详细信息,请参阅“ 修补类实例”一节)。

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

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

类似地,类通过命名引用进行pickle,因此适用于unpickling环境中的相同限制。请注意,不会对类的代码或数据进行pickle,因此在以下示例attr中,在unpickling环境中不会还原class属性:

class Foo:
    attr = 'A class attribute'

picklestring = pickle.dumps(Foo)

 

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

类似地,当类实例被pickle时,它们的类的代码和数据不会与它们一起被pickle。仅腌制实例数据。这是故意完成的,因此您可以修复类中的错误或向类添加方法,并仍然加载使用该类的早期版本创建的对象。如果您计划拥有可以看到类的许多版本的长寿命对象,那么在对象中放置版本号可能是值得的,这样可以通过类的__setstate__()方法进行适当的转换。

酸洗类实例

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

在大多数情况下,不需要额外的代码来使实例可选。默认情况下,pickle将通过内省检索实例的类和属性。在对类实例进行unpickled时,__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__
在协议2和更新版本中,实现该__getnewargs_ex__()方法的类 可以指定__new__()在取消排序时传递给该方法的值 。该方法必须返回一对 ,其中args是位置参数的元组,并且kwargs是用于构造对象的命名参数的字典。这些将在unpickling时传递给方法。(args, kwargs)__new__()

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

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

object.__getnewargs__
此方法的用途与此类似__getnewargs_ex__(),但仅支持位置参数。它必须返回一个参数元组,这些参数 args__new__()在unpickling时传递给方法。

__getnewargs__()如果__getnewargs_ex__()已定义,则不会被调用。

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

object.__getstate__
类可以进一步影响他们的实例被腌制的方式; 如果类定义了方法__getstate__(),则调用它,并将返回的对象作为实例的内容进行pickle,而不是实例的字典的内容。如果该__getstate__()方法不存在,__dict__则像往常一样对实例进行pickle。
object.__setstate__状态
在unpickling时,如果类定义__setstate__(),则以unpickled状态调用它。在这种情况下,不要求状态对象是字典。否则,pickle状态必须是字典,并且其项目将分配给新实例的字典。

注意

如果__getstate__()返回false值,则__setstate__() 在unpickling时不会调用该方法。

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

注意

在在unpickle时,一些方法,如__getattr__(), __getattribute__()__setattr__()可在该实例调用。如果这些方法依赖于某些内部不变量为真,则该类型应该实现__getnewargs__()或 __getnewargs_ex__()建立这样的不变量; 否则,既__new__()不会也__init__()不会被召唤。

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

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

object.__reduce__
该接口目前定义如下。该__reduce__()方法不带参数,并且应返回字符串或最好返回元组(返回的对象通常称为“reduce value”)。

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

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

  • 将调用的可调用对象,用于创建对象的初始版本。
  • 可调用对象的参数元组。如果callable不接受任何参数,则必须给出一个空元组。
  • 可选地,对象的状态,将__setstate__()如前所述传递给对象的 方法。如果对象没有这样的方法,则该值必须是字典,并且它将被添加到对象的__dict__属性中。
  • 可选地,迭代器(而不是序列)产生连续的项目。这些项目将使用obj.append(item)或批量使用附加到对象 obj.extend(list_of_items)。这主要是用于列表的子类,但可以通过其他类,只要他们使用append(),并extend()用适当的签名方法。(无论append()extend()使用取决于哪泡菜协议版本被用作以及项目追加的次数,所以两者都必须被支持。)
  • 可选地,迭代器(不是序列)产生连续的键值对。这些项目将使用存储到对象。这主要用于字典子类,但只要它们实现,就可以被其他类使用。obj[key] = value__setitem__()
object.__reduce_ex__协议
或者,__reduce_ex__()可以定义方法。唯一的区别是这个方法应该采用单个整数参数,即协议版本。在定义时,pickle将优先于该__reduce__() 方法。此外,__reduce__()自动成为扩展版本的同义词。此方法的主要用途是为较旧的Python版本提供向后兼容的reduce值。

外部对象的持久性

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

这种持久性ID的解析不是由pickle 模块定义的; 它将委托此分辨率对皮克勒和Unpickler会,用户定义的方法persistent_id()和 persistent_load()分别。

要挑选具有外部持久性id的对象,pickler必须具有一个自定义persistent_id()方法,该方法将对象作为参数并返回None该对象的持久ID或持久ID。当None返回时,只需皮克勒泡菜对象为正常。当返回持久性ID字符串时,pickler将对该对象以及标记进行pickle,以便unpickler将其识别为持久ID。

要取消排列外部对象,unpickler必须具有一个自定义 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()

 

发货表

如果想要自定义某些类的酸洗而不打扰任何依赖于酸洗的其他代码,那么可以使用私有调度表创建一个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默认情况下所有will的实例共享相同的调度表。使用该copyreg模块的等效代码 是

copyreg.pickle(SomeClass, reduce_SomeClass)
f = io.BytesIO()
p = pickle.Pickler(f)

 

处理有状态对象

这是一个示例,显示如何修改类的酸洗行为。本TextReader类打开一个文本文件,并返回每一次它的行号和行内容,readline()方法被调用。如果 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!'

 

限制全局

默认情况下,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

 

正如我们的例子所示,你必须小心你允许被打开的东西。因此,如果担心安全性,您可能需要考虑替代方案,例如编组API 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)

 

以下示例读取生成的pickle数据。

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] 提出的例外可能是一个ImportError或一个, AttributeError但它可能是其他的东西。
[4] copy模块使用此协议进行浅层和深层复制操作。
[5] 对字母数字字符的限制是由于协议0中的持久ID由换行符分隔。因此,如果持久性ID中出现任何类型的换行符,则生成的pickle将变得不可读。