11.1. pickle — Python 对象序列化

pickle模块实现了一个基本但Function强大的算法,用于对 Python 对象结构进行序列化和反序列化。 “ Pickling”是将 Python 对象层次结构转换为字节流的过程,而“ unpickling”是逆运算,从而字节流被转换回对象层次的过程。Pickling(和 Pickling)也被称为“序列化”,“编组”,“ [1]”或“拉平”,但是,为了避免混淆,此处使用的术语是“Pickling”和“Pickling”。

本文档介绍了pickle模块和cPickle模块。

Warning

pickle模块对于错误或恶意构建的数据并不安全。切勿挑剔从不可信或未经身份验证的来源收到的数据。

11.1.1. 与其他 Python 模块的关系

pickle模块具有一个优化的表弟,称为cPickle模块。顾名思义,cPickle是用 C 编写的,因此它可以比pickle快 1000 倍。但是,它不支持Pickler()Unpickler()类的子类,因为在cPickle中这些是函数,而不是类。大多数应用程序不需要此Function,并且可以受益于cPickle的改进性能。除此之外,两个模块的接口几乎完全相同。本手册介绍了通用接口,并在必要时指出了区别。在下面的讨论中,我们使用术语“ pickle”共同描述picklecPickle模块。

两个模块产生的数据流保证是可互换的。

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

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

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

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

11.1.2. 数据流格式

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

默认情况下,pickle数据格式使用可打印的 ASCII 表示形式。这比二进制表示要复杂得多。使用可打印 ASCII(以及pickle表示的其他一些 Feature)的最大优点是,出于调试或恢复目的,人们可以使用标准文本编辑器读取腌制的文件。

当前有 3 种不同的协议可用于 Pickling。

有关更多信息,请参考 PEP 307

如果未指定* protocol ,则使用协议 0.如果将 protocol *指定为负值或HIGHEST_PROTOCOL,则将使用可用的最高协议版本。

在版本 2.3 中进行了更改:引入了* protocol *参数。

pass指定* protocol *版本> = 1,可以选择效率更高的二进制格式。

11.1.3. Usage

要序列化对象层次结构,首先创建一个 Pickler,然后调用 Pickler 的dump()方法。要对数据流进行反序列化,请首先创建一个 unpickler,然后调用 unpickler 的load()方法。 pickle模块提供以下常量:

2.3 版的新Function。

Note

确保始终以二进制模式打开使用协议> = 1 创建的 pickle 文件。对于旧的基于 ASCII 的 pickle 协议 0,只要保持一致,就可以使用文本模式或二进制模式。

在二进制模式下以协议 0 编写的 pickle 文件将包含单独的换行符作为换行符,因此,在记事本或其他不支持此格式的编辑器中查看时,它们看起来“很有趣”。

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

如果Ellipsis* protocol 参数,则使用协议 0.如果将 protocol *指定为负值或HIGHEST_PROTOCOL,则将使用最高协议版本。

在版本 2.3 中进行了更改:引入了* protocol *参数。

此Function自动确定数据流是否以二进制模式写入。

如果Ellipsis* protocol 参数,则使用协议 0.如果将 protocol *指定为负值或HIGHEST_PROTOCOL,则将使用最高协议版本。

在版本 2.3 中进行了更改:添加了* protocol *参数。

pickle模块还定义了三个 exception:

pickle模块还导出两个可调用对象[2]PicklerUnpickler

如果Ellipsis* protocol 参数,则使用协议 0.如果将 protocol *指定为负值或HIGHEST_PROTOCOL,则将使用最高协议版本。

在版本 2.3 中进行了更改:引入了* protocol *参数。

Pickler个对象定义一个(或两个)公共方法:

Note

在 Python 2.3 之前,clear_memo()仅在由cPickle创建的选取器上可用。在pickle模块中,选取器有一个名为memo的实例变量,它是 Python 字典。因此,要清除pickle模块选取器的备注,可以执行以下操作:

mypickler.memo.clear()

不需要支持旧版本 Python 的代码应该只使用clear_memo()

可以多次调用同一Pickler实例的dump()方法。然后,它们必须与对相应Unpickler实例的load()方法的相同调用次数匹配。如果同Pair象被多个dump()调用腌制,则load()将全部产生对同Pair象的引用。 [3]

Unpickler个对象定义为:

Unpickler个对象具有一个(或两个)公共方法:

此方法自动确定数据流是否以二进制模式写入。

注意: noload()方法当前仅在使用cPickle模块创建的Unpickler对象上可用。 pickle模块Unpickler没有noload()方法。

11.1.4. 什么可以腌制和不腌制?

可以腌制以下类型:

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

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

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

class Foo:
    attr = 'a class attr'

picklestring = pickle.dumps(Foo)

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

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

11.1.5. pickle 协议

本节描述了“Pickling 协议”,该协议定义了 Pickling 程序/取消 Pickling 程序和要序列化的对象之间的接口。该协议为您提供了一种定义,定制和控制对象序列化和反序列化方式的标准方法。本节中的描述未涵盖可用于使不 Pickling 数据流中的不 Pickling 环境更安全的特定自定义项;有关详情,请参见第Subclassing Unpicklers节。

11.1.5.1. 腌制和解开普通类实例

obj = C.__new__(C, *args)

其中* args *是在原始对象上调用getnewargs()的结果;如果没有getnewargs(),则假定为空的 Tuples。

Note

对于new-style class es,如果getstate()返回错误值,则不会调用setstate()方法。

Note

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

11.1.5.2. Pickling 和解酸扩展类型

如果返回一个字符串,它将命名一个全局变量,其内容将被正常腌制。 reduce()返回的字符串应该是对象相对于其模块的本地名称; pickle 模块搜索模块名称空间以确定对象的模块。

返回一个 Tuples 时,它的长度必须在 2 到 5 个元素之间。可选元素可以Ellipsis,也可以提供None作为其值。将该 Tuples 的内容照常进行腌制,并在去腌制时用于重建对象。每个元素的语义是:

在解开环境中,此对象必须是一个类,注册为“安全构造函数”的可调用对象(请参见下文),或者它必须具有带有真实值的属性__safe_for_unpickling__。否则,将在解酸环境中引发UnpicklingError。注意,像往常一样,可调用对象本身是按名称腌制的。

在版本 2.5 中更改:以前,此参数也可以是None

object类同时实现reduce()reduce_ex();但是,如果子类覆盖reduce()而不是reduce_ex(),则reduce_ex()实现会检测到此情况并调用reduce()

在要腌制的对象上实现reduce()方法的另一种方法是在copy_reg模块中注册可调用对象。该模块为程序提供了一种注册“归约函数”和用户定义类型的构造函数的方法。归约函数具有与上述reduce()方法相同的语义和接口,不同之处在于它们使用单个参数(要腌制的对象)调用。

如上所述,出于析出的目的,已注册的构造函数被视为“安全构造函数”。

11.1.5.3. Pickling 和去 Pickling 外部物体

为了实现对象持久性,pickle模块支持对腌制数据流外部的对象进行引用的概念。此类对象由“永久 ID”引用,该 ID 只是可打印 ASCII 字符的任意字符串。 pickle模块未定义此类名称的解析;它将把此解决方案委派给 Pickler 和 UnPickler 上用户定义的Function。 [7]

要定义外部持久 ID 解析,您需要设置 pickler 对象的persistent_id属性和 unpickler 对象的persistent_load属性。

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

要解除对外部对象的限制,解除请求程序必须具有一个自定义persistent_load()函数,该函数接受一个永久 ID 字符串并返回引用的对象。

这是一个愚蠢的例子,可能揭示了更多的信息:

import pickle
from cStringIO import StringIO

src = StringIO()
p = pickle.Pickler(src)

def persistent_id(obj):
    if hasattr(obj, 'x'):
        return 'the value %d' % obj.x
    else:
        return None

p.persistent_id = persistent_id

class Integer:
    def __init__(self, x):
        self.x = x
    def __str__(self):
        return 'My name is integer %d' % self.x

i = Integer(7)
print i
p.dump(i)

datastream = src.getvalue()
print repr(datastream)
dst = StringIO(datastream)

up = pickle.Unpickler(dst)

class FancyInteger(Integer):
    def __str__(self):
        return 'I am the integer %d' % self.x

def persistent_load(persid):
    if persid.startswith('the value '):
        value = int(persid.split()[2])
        return FancyInteger(value)
    else:
        raise pickle.UnpicklingError, 'Invalid persistent id'

up.persistent_load = persistent_load

j = up.load()
print j

cPickle模块中,也可以将取消选取程序的persistent_load属性设置为 Python 列表,在这种情况下,当取消选取程序达到持久 ID 时,持久 ID 字符串将简单地附加到此列表中。存在此Function的目的是,可以“嗅探”pickle 数据流以进行对象引用,而无需实际实例化 pickle 中的所有对象。 [8]persistent_load设置为列表通常与 Unpickler 上的noload()方法结合使用。

11.1.6. 子类化 Unpicklers

默认情况下,取消腌制将导入它在腌制数据中找到的任何类。您可以pass自定义取消选取器来精确控制哪些内容未被选取以及哪些内容被调用。不幸的是,具体取决于您使用的是pickle还是cPickle,您的操作方式有所不同。 [9]

pickle模块中,您需要从Unpickler派生一个子类,从而覆盖load_global()方法。 load_global()应该从 pickle 数据流中读取两行,其中第一行将是包含该类的模块的名称,第二行将是该实例的类的名称。然后,它查找该类,可能会导入模块并挖掘属性,然后将找到的内容附加到取消拾取程序的堆栈中。稍后,该类将被分配给空类的__class__属性,这是一种神奇地创建实例而无需调用其类init()的方式。您的工作(您应该选择接受)是load_global()推入卸货员的烟囱,这是您认为可以安全卸料的任何类的已知安全版本。产生这样的类取决于您。或者,如果您想禁止实例的全部取消操作,则可能会引发错误。如果这听起来像是骇客,那是对的。请参考源代码以使此工作。

使用cPickle可以使事情变得更干净,但幅度不大。要控制哪些东西不被 Pickling,您可以将不 Pickling 的find_global属性设置为一个函数或None。如果为None,则try解除实例修复的任何try都会引发UnpicklingError。如果它是一个函数,则它应该接受一个模块名和一个类名,并返回相应的类对象。它负责查找类并执行任何必要的导入,并且可能引发错误,以防止类的实例被解开。

这个故事的寓意是,您应该对应用程序释放的字符串的来源非常小心。

11.1.7. Example

对于最简单的代码,请使用dump()load()函数。请注意,将自动腌制并正确还原自引用列表。

import pickle

data1 = {'a': [1, 2.0, 3, 4+6j],
         'b': ('string', u'Unicode string'),
         'c': None}

selfref_list = [1, 2, 3]
selfref_list.append(selfref_list)

output = open('data.pkl', 'wb')

# Pickle dictionary using protocol 0.
pickle.dump(data1, output)

# Pickle the list using the highest protocol available.
pickle.dump(selfref_list, output, -1)

output.close()

以下示例读取所得的腌制数据。读取包含 pickle 的文件时,应以二进制模式打开文件,因为无法确定是否使用 ASCII 或二进制格式。

import pprint, pickle

pkl_file = open('data.pkl', 'rb')

data1 = pickle.load(pkl_file)
pprint.pprint(data1)

data2 = pickle.load(pkl_file)
pprint.pprint(data2)

pkl_file.close()

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

#!/usr/local/bin/python

class TextReader:
    """Print and number lines in a text file."""
    def __init__(self, file):
        self.file = file
        self.fh = open(file)
        self.lineno = 0

    def readline(self):
        self.lineno = self.lineno + 1
        line = self.fh.readline()
        if not line:
            return None
        if line.endswith("\n"):
            line = line[:-1]
        return "%d: %s" % (self.lineno, line)

    def __getstate__(self):
        odict = self.__dict__.copy() # copy the dict since we change it
        del odict['fh']              # remove filehandle entry
        return odict

    def __setstate__(self, dict):
        fh = open(dict['file'])      # reopen file
        count = dict['lineno']       # read from file...
        while count:                 # until line count is restored
            fh.readline()
            count = count - 1
        self.__dict__.update(dict)   # update attributes
        self.fh = fh                 # save the file object

用法示例可能是这样的:

>>> import TextReader
>>> obj = TextReader.TextReader("TextReader.py")
>>> obj.readline()
'1: #!/usr/local/bin/python'
>>> obj.readline()
'2: '
>>> obj.readline()
'3: class TextReader:'
>>> import pickle
>>> pickle.dump(obj, open('save.p', 'wb'))

如果您希望pickle跨 Python 进程运行,请在 continue 之前启动另一个 Python 会话。相同的过程或新的过程都可能发生以下情况。

>>> import pickle
>>> reader = pickle.load(open('save.p', 'rb'))
>>> reader.readline()
'4:     """Print and number lines in a text file."""'

See also

  • Module copy_reg

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

  • Module shelve

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

  • Module copy

  • 浅而深的对象复制。

  • Module marshal

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

11.2. cPickle —更快的 pickle

cPickle模块支持 Python 对象的序列化和反序列化,提供的接口和Function与pickle模块几乎相同。有几个差异,最重要的是性能和可分类性。

首先,cPickle的速度比pickle快 1000 倍,因为前者是用 C 语言实现的。其次,在cPickle模块中,可调用的Pickler()Unpickler()是函数,而不是类。这意味着您不能使用它们来派生自定义的 Pickling 和不 Pickling 子类。大多数应用程序不需要此Function,并且应该受益于cPickle模块大大提高的性能。

picklecPickle产生的 pickle 数据流是相同的,因此可以将picklecPickle与现有的 pickle 互换使用。 [10]

cPicklepickle之间在 API 上还有其他细微差别,但是对于大多数应用程序来说,它们是可以互换的。 pickle模块文档中提供了更多文档,其中包括已记录差异的列表。

Footnotes

首页