Python 对象序列化——pickle and cPickle
从这篇文章粗略翻译的pickle and cPickle
pickle模块可以实现任意的Python对象转换为一系列字节(即序列化对象)的算法。这些字节流可以 被传输或存储,接着也可以重构为一个和原先对象具有相同特征的新对象。
cPickle模块实现了同样的算法,但它是用c而不是python。因此,它比python实现的快上好几倍, 但是不允许使用者去继承Pickle。如果继承对于你的使用不是很重要,那么你大可以使用cPickle。
Woring: pickle的文档明确的表明它不提供安全保证。所以慎用pickle来作为内部进程通信或者数
据存储,也不要相信那些你不能验证安全性的数据。
Importing
通常优先试用 cPickle,只有当 cPickle 无法正常 import 的时候,采用 pickle 来替代。
try:
import cPickle as pickle
except:
import pickle
Encoding and Decoding Data in Strings
第一个示例是将数据结构编码为字符串,然后输出到控制台。例子中数据结构完全由基本类型组成。pickle 可以编码任意类的实例,就像下面栗子中演示的那样:用 pickle.dumps() 来创建对象的字符串表示。
try:
import cPickle as pickle
except:
import pickle
import pprint
data = [ { 'a':'A', 'b':2, 'c':3.0 } ]
print 'DATA:',
pprint.pprint(data)
data_string = pickle.dumps(data)
print 'PICKLE:', data_string
pickle 默认试用 ASSCII 字符串来进行编码解码。也支持效率更高的二进制格式,但是下面的例子为了方便阅读,还是使用了 ASSCII 码。
$ 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、pipe、etc.然后你可以读取文件并unpickle 这些数据来构造一个新的对象。
try:
import cPickle as pickle
except:
import pickle
import pprint
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
Working with Streams
除了 dumps() 跟 loads(), pickle 还有其他比较方便的方法来操作类文件流。可以同时写入多个对象到一个 stream 中,然后对象数量与大小的时候从 stream 读取他们。
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 缓冲区来模拟streams,这样我们在建立可读流的时候可以玩一些技巧。一些接单的数据库格式也可以使用 pickles 来存储数据,当然,如果试用 shelve 来存储会更加简单。
$ python pickle_stream.py
WRITING: pickle (elkcip)
WRITING: cPickle (elkciPc)
WRITING: last (tsal)
READ: pickle (elkcip)
READ: cPickle (elkciPc)
READ: last (tsal)
除了存储数据,pickles 用来做内部通信的机会也很多。举个例子:用 os.fork() 和 os.pipe() 可以建立一个工作进程,然后这个进程会从管道中读取数据并把结果传递给另外一个管道。因为这些代码是用来管理worker pool 跟 发送任务跟接受任务的,没有什么特殊的内容,所以这些核心代码可以被拿来重复利用。如果你在试用 pipe 或者 sockets,那么在 dumping完对象之后,不要忘记刷新它们并通过其间的连接将数据推送到另外一个进程。如果你不想自己写 worker pool manager 的话,可以看一下multiprocessing
。
Problems Reconstructing Objects
需要注意的是,在序列化实例的时候,我们只是对于数据来进行序列化,而无法对类的定义进行序列化。
下面的栗子:
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)
下面是一个会报错的栗子:
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'
我们通过从原来的脚本中import 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)
像sockets, file handles, database connections ...
这些数据类型是无法被序列化的,我们在处理类似数据类型的时候不得不特殊处理。而利用pickle protocol 则可以控制序列化的细节。
class Data(object):
def __init__(self, x, y):
self._x = x
self._y = y
def __getstate__(self):
d = self.__dict__.copy()
del d["_y"]
return d
def __setstate__(self, state):
self.__dict__.update(state)
d = Data(10, 20)
s = cPickle.dumps(d, 2)
d2 = cPickle.loads(s)
##d2.__dict__
##{'_x': 10}