Table Of Contents

Previous topic

PyMOTW: os(4)

Next topic

PyMOTW: glob

This Page

PyMOTW: pickle & cPickle

  • 模块:pickle 和 cPickle
  • 目的: Python对象序列化
  • python版本:pickle至少1.4, cPickle 至少1.5

Python对象序列化

描述

pickle模块可以实现任意的Python对象转换为一系列字节(即序列化对象)的算法. 这些字节流可以被传输或存储, 接着也可以重构为一个和原先对象具有相同特征的新对象.

cPickle模块实现了同样的算法, 但它是用c而不是python. 因此, 它比python实现的快上好几倍, 但是不允许使用者去继承Pickle. 如果继承对于你的使用不是很重要, 那么你大可以使用cPickle.

Note

pickle的文档清晰的表明它不提供安全保证. 所以慎用pickle来作为内部进程通信或者数据存储, 也不要相信那些你不能验证安全性的数据.

例子

第一个pickle示例是将一个数据结构编码为一个字符串, 然后将其输出到控制台.

try:
    import cPickle as pickle
except:
    import pickle
import pprint

我们首先尝试导入cPickle, 并指定别名为”pickle”. 如果因为某种原因导入pickle失败, 我们则导入由Python实现的pickle模块. 如果cPickle是可用的, 会给我们带来更快的实现, 但如果不可用, 也会有正确的实现.

接下来, 我们定义一个完全由基本类型组成的数据结构. 任何类的实例都可被pickle, 这会在下一个例子中表述. 我选择基本数据类型以便更简单的示范.

data = [ { 'a':'A', 'b':2, 'c':3.0 } ]
print 'DATA:',
pprint.pprint(data)

现在我们就可以使用pickle.dumps()来创建数据值的字符串表示.

data_string = pickle.dumps(data)
print 'PICKLE:', data_string

默认情况下, pickle仅使用ASCII字符. 也可以使用高效的二进制格式. 但这些示例依然使用了ASCII格式.

$ python pickle_string.py
DATA:[{'a': 'A', 'b': 2, 'c': 3.0}]
PICKLE: (lp1
(dp2
S'a'
S'A'
sS'c'
F3
sS'b'
I2
sa.

一旦数据被序列化, 你就可以把他写入到文件、socket、管道等等中. 之后你可以读取这个文件, unpickle这些数据来构造具有相同值的新对象.

data1 = [ { 'a':'A', 'b':2, 'c':3.0 } ]
print 'BEFORE:',
pprint.pprint(data1)

data1_string = pickle.dumps(data1)

data2 = pickle.loads(data1_string)
print 'AFTER:',
pprint.pprint(data2)

print 'SAME?:', (data1 is data2)
print 'EQUAL?:', (data1 == data2)

正像你看到的那样, 新构造的对象等于原来的对象, 但他们又不是相同的对象. 这里不足为奇.

$ python pickle_unpickle.py
BEFORE:[{'a': 'A', 'b': 2, 'c': 3.0}]
AFTER:[{'a': 'A', 'b': 2, 'c': 3.0}]
SAME?: False
EQUAL?: True

pickle除了提供dumps()和loads(), 还提供非常方便的函数用于操作类文件流. 支持同时写多个对象到同一个流中, 然后在不知道有多少个对象或不知道它们有多大时, 能够从这个流中读取多个对象也是可能的.

try:
    import cPickle as pickle
except:
    import pickle
import pprint
from StringIO import StringIO

class SimpleObject(object):
    def __init__(self, name):
        self.name = name
        l = list(name)
        l.reverse()
        self.name_backwards = ''.join(l)
        return

data = []
data.append(SimpleObject('pickle'))
data.append(SimpleObject('cPickle'))
data.append(SimpleObject('last'))

# Simulate a file with StringIO
out_s = StringIO()

# Write to the stream
for o in data:
    print 'WRITING: %s (%s)' % (o.name, o.name_backwards)
    pickle.dump(o, out_s)
    out_s.flush()

# Set up a read-able stream
in_s = StringIO(out_s.getvalue())

# Read the data
while True:
    try:
        o = pickle.load(in_s)
    except EOFError:
        break
    else:
        print 'READ: %s (%s)' % (o.name, o.name_backwards)

这个例子使用StringIO缓冲区来模拟流, 因此我们在建立可读流时得玩点小花样. 一个简单数据库格式也可以使用pickle来存储对象, 虽然使用shelve模块可能会更简单.

$ python pickle_stream.py
WRITING: pickle (elkcip)
WRITING: cPickle (elkciPc)
WRITING: last (tsal)
READ: pickle (elkcip)
READ: cPickle (elkciPc)
READ: last (tsal)

除了用于存储数据, pickle在用于内部进程通信时是非常灵活的. 比如, 使用os.fork()和os.pipe(), 可以建立一些工作进程, 它们从一个管道中读取任务说明并把结果输出到另一个管道. 操作这些工作池、发送任务和接受反应的核心代码可以重复利用, 因为任务和反应对象不是一个特殊的类. 如果你使用管道或者sockets, 就不要忘记在dump每个对象后刷新它们并通过其间的连接将数据推送到另外一个进程.

在处理自定义类时, 你应该保证这些被pickled的类会在进程名空间出现. 只有数据实例才能被pickle, 而不能是定义的类. 在unpickle时, 类的名字被用于寻找构造器以便创建新对象. 接下来这个例子, 是将一个类实例写入到文件中:

try:
    import cPickle as pickle
except:
    import pickle
    import sys

class SimpleObject(object):
    def __init__(self, name):
        self.name = name
        l = list(name)
        l.reverse()
        self.name_backwards = ''.join(l)
        return

if __name__ == '__main__':
    data = []
    data.append(SimpleObject('pickle'))
    data.append(SimpleObject('cPickle'))
    data.append(SimpleObject('last'))
    try:
        filename = sys.argv[1]
        except IndexError:
        raise RuntimeError('Please specify a filename as an argument to %s' % sys.argv[0])
    out_s = open(filename, 'wb')
    try:
        # Write to the stream
        for o in data:
            print 'WRITING: %s (%s)' % (o.name, o.name_backwards)
            pickle.dump(o, out_s)
    finally:
        out_s.close()

当我运行这个脚本时, 它会创建名为我在命令行中输入的参数的文件:

$ python pickle_dump_to_file_1.py test.dat
WRITING: pickle (elkcip)
WRITING: cPickle (elkciPc)
WRITING: last (tsal)

一个简单的尝试将刚才的pickle结果对象装载进来可以是如下的样子:

try:
    import cPickle as pickle
except:
    import pickle
import pprint
from StringIO import StringIO
import sys

try:
    filename = sys.argv[1]
except IndexError:
    raise RuntimeError('Please specify a filename as an argument to %s' % sys.argv[0])

in_s = open(filename, 'rb')
try:
    # Read the data
    while True:
        try:
            o = pickle.load(in_s)
        except EOFError:
            break
        else:
            print 'READ: %s (%s)' % (o.name, o.name_backwards)
finally:
    in_s.close()

这个版本失败了, 因为这里没有可用的SimpleObject类.

$ python pickle_load_from_file_1.py test.dat
Traceback (most recent call last):
File "pickle_load_from_file_1.py", line 52, in
   o = pickle.load(in_s)
AttributeError: 'module' object has no attribute 'SimpleObject'

一个正确版本, 它从pickle_dump_to_file_1导入了SimpleObject类, 可以成功运行. 增加:

from pickle_dump_to_file_1 import SimpleObject

到导入列表的最后, 然后运行这个脚本:

$ python pickle_load_from_file_2.py test.dat
READ: pickle (elkcip)
READ: cPickle (elkciPc)
READ: last (tsal)

在pickle那些不能被pickle的数据(如sockets、文件句柄、数据库连接等等)时, 需要考虑一些特殊之处. 那些不能被pickle的类可以定义__getstate__()和__setstate__()来返回实例在被pickle时的状态. 新风格的类也可以定义__getnewargs__(), 它返回传递给类内存分配者(C.__new__())的参数. 关于这些特征的更详细的使用描述可以在标准库文档中找到.