在实际应用中,序列化的场景很常见,如:在磁盘上保存当前程序的状态数据以便重启的时候能够重新加载; 多用户或者分布式系统中数据结构的网络传输时,可以将数据序列化后发送给一个可信网络对端, 接收者进行反序列化后便可以重新恢复相同的对象;session和 cache的存储等。序列化, 简单地说就是把内存中的数据结构在不丢失其身份和类型信息的情况下转成对象的文本或二进制表示的过程。 对象序列化后的形式经过反序列化过程应该能恢复为原有对象。
Python中有很多支持序列化的模块,如 pickle
、 json
、 marshap
和 shelve
等。
最广为人知的为 pickle
,下面来仔细分析这个模块。
pickle估计是最通用的序列化模块了,它还有个C语言的实现 cPickle
, 相比 pickle
来说 具有较好的性能,
其速度大概是pickle
的 1000 倍,因此在大多数应用程序中应该优先使用 cpikle
。
注意:cPickle
除了不能被继承之外,它们两者的使用基本上区别不大,除有持殊情况, 本节将不再做具体区分。
dump()
和 load()
函数
pickle
中最主要的两个函数对为 dump()
和 load()
,分別用来进行对象的序列化和反序列化。
pickle.dump(obj, file[, protocol])
: 序列化数据到一个文件描述符(一个打开的文件、 套接字等参数。obj
表示需要序列化的对象,包括布尔、数字、宇符串、字节数组、None
、列表、元组、字典和集合等基本数据类型。 此外pickle
还能够处理循环,递归引用对象、类、函数以及类的实例等。 参数file
支持write
方法的文件句柄, 可以为真实的文件,也可以是 StringIO 对象等。protocol
为序列化使用的协议版本, 0表示 ASCII协议所序列化的对象使用可打印的ASCII码表示;|
表示老式的二进制协议; 2表示2.3版本引入的新二进制协议.比以前的更高效。其中协议0和1兼容老版本的 Python。protocol
默认值为 0 。load(file)
: 表示把文件中的对象恢复为原来的对象,这个过程也披称为反序列化。
来看一下 load()
和 dump()
的示例。
import pickle
my_data={"name":"python","type":"language","version":"2.7.5"}
fq=open("xx_pick.dat","wb")
pickle.dump(my_data,fq)
fq.close()
fp=open("xx_pick.dat","rb")
out=pickle.load(fp)
print(out)
{'name': 'python', 'type': 'language', 'version': '2.7.5'}
pickle
之所以能成为通用的序列化模块,与其良好的特性是分不开的,总结为以下几点:
- 接口简单,容易使用。通过
dump()
和load()
便可轻易实现序列化和反序列化。 pickle
的存储格式具有通用性,能够被不同平台的Pythcm解析器共享,比如, Linux 下序列化的格式文件可以在Windows平台的Python解析器上进行反序列化,兼容性较好。- 支持的数据类型广泛。如数字、布尔值、字符串,
只包含可序列化对象的元组、字 典、列表等,非嵌套的函数、类以及通过类的
_dict_
或者_getstate_
可以返回序列化对象的实例等。 pickle
模块是可以扩展的。对于实例对象,pickle
在还原对象的时候一般是不调用__init__()
函数的, 如果要调用__init__()
进行初始化,对于古典类可以在类定义中提供__getinitargs__()
函数, 并返回一个元组,当进行unpickle
的时候,Python就会自动调用__init__()
, 并把__getinitargs__()
中返回的元组作为参数传递给__init__()
,而对于新式类, 可以提供__getnewargs__()
来提供对象生成时候的参数, 在unpickle
的时候以Class.__new__(Class, *arg)
的方式创建对象。 对于不可序列化的对象,如sockets
、文件句柄、数据库连接等, 也可以通过实现pickle协议来解决这些局限, 主要是通过特殊方法__gestate__()
和__setstate__()
来返回实例在被pickle
时的状态。
来看以下示例:
import pickle
class TextReader:
#文件名称 #打开文件的句柄
def __init__ (self,filename):
self.filename=filename
self.file=open(filename)
self.postion=self.file.tell()
def readline(self):
line=self.file.readline()
self.postion=self.file.tell()
if not line:
return None
if line.endswith("\n"):
line=line[:-1]
return "%i:%s "%(self.postion, line)
def __getstate__(self): #记录文件被pickle时候的状态
state = self. __dict__.copy()# If 获取被 pickle 时的字典信息
del state['file']
return state
def __setstat__(self, state) : #设盥反序列化后的状态
self.dict.update(state)
file=open(self.filename)
self.file = file
reader =TextReader("xx_zen.txt")
print (reader.readline())
None
print(reader.readline())
None
fr
在 dumps
的时候会默认调用 getstate_
。
s = pickle. dumps (reader)
在 loads
的时候会默认调用 setstate_
。
newsreader = pickle.loads(s)
#print(newsreader.readline())
- 能够向动维护对象间的引用。如果一个对象上存在多个引用,
pickle
后不会改变对象 间的引用,并且能够自动处理循环和递归引用。
a=['a','b']
b=a
b.append('c')
p=pickle.dumps((a,b))
a1,b1=pickle.loads(p)
a1
['a', 'b', 'c']
b1
['a', 'b', 'c']
a1.append('d')
b1
['a', 'b', 'c', 'd']
但 pickle
使用也存在以下一些限制:
pickle
不能保证操作的原子性。pickle
并不是原子操作, 也就是说在一个pickle
凋用中如果发生异常,可能部分数据已经被保存, 另外如果对象处于深递归状态,那么可能超出Python的最大递归深度。 递归深度可以通过sys.setrecursionlimit()
进行扩展。pickle
存在安全性问题,Python的文档清晰地表明它不提供安全性保证, 因此对于一个从不可信的数据源接收的数据不要轻易进行反序列化。 由于loads()
可以接收字符串作为参数,这意味着精心设计的字符串给入侵提供了一种可能。 在Pthon解释器中 输人代码pickle.loads("cos\nsystem\n(S'dir'\ntR.")
便可査看当前目录下所有文件。 如果将dir
替换为其他更具有破坏性的命令将会带来安全隐患。如果要进一步提高安全性, 用户可以通过继承类pickle.Unpickler
并重写find_class()
方法来实现。pickle
协议是Python特定的,不同语言之间的兼容性难以保障。 用Python创建的pickle
文件可能其他语言不能使用,如Perl 、 PHP、Java等。