详解Python之数据序列化(json、pickle、shelve)
一、前言 1. 现实需求 每种编程语言都有各自的数据类型,其中面向对象的编程语言还允许开发者自定义数据类型(如:自定义类),Python也是一样。很多时候我们会有这样的需求:
2.数据格式 如果要将一个系统内的数据通过网络传输给其它系统或客户端,我们通常都需要先把这些数据转化为字符串或字节串,而且需要规定一种统一的数据格式才能让数据接收端正确解析并理解这些数据的含义。XML 是早期被广泛使用的数据交换格式,在早期的系统集成论文中经常可以看到它的身影;如今大家使用更多的数据交换格式是JSON(JavaScript Object Notation),它是一种轻量级的数据交换格式。JSON相对于XML而言,更加加单、易于阅读和编写,同时也易于机器解析和生成。除此之外,我们也可以自定义内部使用的数据交换格式。 如果是想把数据持久化到本地磁盘,这部分数据通常只是供系统内部使用,因此数据转换协议以及转换后的数据格式也就不要求是标准、统一的,只要本系统内部能够正确识别即可。但是,系统内部的转换协议通常会随着编程语言版本的升级而发生变化(改进算法、提高效率),因此通常会涉及转换协议与编程语言的版本兼容问题,下面要时候的pickle协议就是这样一个例子。 3. 序列化/反序列化 将对象转换为可通过网络传输或可以存储到本地磁盘的数据格式(如:XML、JSON或特定格式的字节串)的过程称为序列化;反之,则称为反序列化。 4.相关模块 本节要介绍的就是Python内置的几个用于进行数据序列化的模块:
二、json模块 大部分编程语言都会提供处理json数据的接口,Python 2.6开始加入了json模块,且把它作为一个内置模块提供,无需下载即可使用。 1. 序列化与反序列化 Python的JSON模块 序列化与反序列化的过程分别叫做:encoding 和 decoding。
json模块提供了以下两个方法来进行序列化和反序列化操作: # 序列化:将Python对象转换成json字符串 dumps(obj,skipkeys=False,ensure_ascii=True,check_circular=True,allow_nan=True,cls=None,indent=None,separators=None,default=None,sort_keys=False,**kw) # 反序列化:将json字符串转换成Python对象 loads(s,encoding=None,object_hook=None,parse_float=None,parse_int=None,parse_constant=None,object_pairs_hook=None,**kw) 除此之外,json模块还提供了两个额外的方法允许我们直接将序列化后得到的json数据保存到文件中,以及直接读取文件中的json数据进行反序列化操作: # 序列化:将Python对象转换成json字符串并存储到文件中 dump(obj,fp,**kw) # 反序列化:读取指定文件中的json字符串并转换成Python对象 load(fp,**kw) 2. JSON与Python之间数据类型对应关系 Python转JSON
JSON转Python
说明:
3. 实例:内置数据类型序列化/反序列化 序列化 # 序列化 >>> json.dumps({'a':'str','c': True,'e': 10,'b': 11.1,'d': None,'f': [1,2,3],'g':(4,5,6)}) '{"a": "str","c": true,"b": 11.1,"e": 10,"d": null,"g": [4,6],"f": [1,3]}' sort_keys参数: 表示序列化时是否对dict的key进行排序(dict默认是无序的) # 序列化并对key进行排序 >>> json.dumps({'a':'str',6)},sort_keys=True) '{"a": "str",6]}' indent参数: 表示缩进的意思,它可以使得数据存储的格式变得更加优雅、可读性更强;如果indent是一个非负整数或字符串,则JSON array元素和object成员将会被以相应的缩进级别进行打印输出;如果indent是0或负数或空字符串,则将只会插入换行,不会有缩进。 # 序列化并对key进行排序及格式化输出 >>> print(json.dumps({'a':'str',sort_keys=True,indent=4)) { "a": "str","f": [ 1,3 ],"g": [ 4,6 ] } separators参数: 尽管indent参数可以使得数据存储的格式变得更加优雅、可读性更强,但是那是通过添加一些冗余的空白字符进行填充的。当json被用于网络数据通信时,应该尽可能的减少无用的数据传输,这样可以节省贷款并加快数据传输速度。json模块序列化Python对象后得到的json字符串中的','号和':'号分隔符后默认都会附加一个空白字符,我们可以通过separators参数重新指定分隔符,从而去除无用的空白字符;
>>> json.dumps({'a':'str',3]}' >>> json.dumps({'a':'str',separators=(',':')) '{"a":"str","c":true,"b":11.1,"e":10,"d":null,"g":[4,"f":[1,3]}' 反序列化 # 反序列化 >>> json.loads('{"a": "str",3]}') {'c': True,'a': 'str','g': [4,'b': 11.1} >>> json.loads('{"a":"str",'b': 11.1} dump()与load()函数示例 # 序列化到文件中 >>> with open('test.json','w') as fp: ... json.dump({'a':'str中国',indent=4) # 反序列化文件中的内容 >>> with open('test.json','r') as fp: ... json.load(fp) {'e': 10,'a': 'str中国',3]} 需要说明的是: 如果试图使用相同的fp重复调用dump()函数去序列化多个对象(或序列化同一个对象多次),将会产生一个无效的JSON文件,也就是说对于一个fp只能调用一次dump()。 4. 实例:自定义数据类型的序列化/反序列化 Python是面向对象的编程语言,我们可以自定义需要的数据类型;实际工作中,我们常常会用到自定义数据类型的序列化与反序列化操作。要实现自定义数据类型的序列化与反序列化有两种方式:
首先来自定义一个数据类型 class Student(object): def __init__(self,name,age,sno): self.name = name self.age = age self.sno = sno def __repr__(self): return 'Student [name: %s,age: %d,sno: %d]' % (self.name,self.age,self.sno) 直接调用dumps()方法会引发TypeError错误: >>> stu = Student('Tom',19,1) >>> print(stu) Student [name: Tom,age: 19,sno: 1] >>> >>> json.dumps(stu) ... TypeError: Student [name: Tom,sno: 1] is not JSON serializable 上面的异常信息中指出:stu对象不可以被序列化为JSON个数的数据。那么我们分别通过“编写转换函数” 和 “继承JSONEncoder和JSONDecoder类” 来实现对这个自定义数据类型的JSON序列化和反序列化。 方法1:编写转换函数 那么这个转换函数要完成哪两个数据类型之间的转换呢? 从上面列出的JSON与Python数据类型的对应表中可知,JSON中的object对应的是Python中的dict,因此要对Python中的自定义数据类型的对象进行序列化,就需要先把这个对象转换成json模块可以直接进行序列化dict类型。由此可知,这个转换函数是要完成的是Python对象(不是JSON对象)与dict之间的相互转换,且序列化时转换过程是“Python对象 --> dict --> JSON object”,反序列化的过程是“JSON object -> dict --> Python对象”。所以,我们需要编写两个转换函数来分别实现序列化和反序列化时的转换过程。 def obj2dict(obj): d = {} d['__class__'] = obj.__class__.__name__ d['__module__'] = obj.__module__ d.update(obj.__dict__) return d def dict2obj(d): if '__class__' in d: class_name = d.pop('__class__') module_name = d.pop('__module__') module = __import__(module_name) class_ = getattr(module,class_name) args = dict((key.encode('ascii'),value) for key,value in d.items()) instance = class_(**args) else: instance = d return instance 继承JSONEncoder实现反序列化时还有一个额外的作用,就是可以通过iterencode()方法把一个很大的数据对象分多次进行序列化,这对于网络传输、磁盘持久化等情景非常有用。 >>> for chunk in MyJSONEncoder().iterencode(stu): ... print(chunk) ... { "__class__" : "Student","name" : "Tom","__module__" : "__main__","sno" : 1,"age" : 19 } 大数据对象序列化网络传输伪代码: for chunk in JSONEncoder().iterencode(bigobject): mysocket.write(chunk) 序列化测试: >>> import json >>> obj2dict(stu) {'sno': 1,'__module__': '__main__','age': 19,'__class__': 'Student','name': 'Tom'} >>> json.dumps(obj2dict(stu)) '{"sno": 1,"__module__": "__main__","age": 19,"__class__": "Student","name": "Tom"}' >>> json.dumps(stu,default=obj2dict) '{"sno": 1,"name": "Tom"}'
反序列化测试: >>> json.loads('{"sno": 1,"name": "Tom"}') {u'sno': 1,u'__module__': u'__main__',u'age': 19,u'name': u'Tom',u'__class__': u'Student'} >>> dict2obj(json.loads('{"sno": 1,"name": "Tom"}')) Student [name: Tom,sno: 1] >>> json.loads('{"sno": 1,"name": "Tom"}',object_hook=dict2obj) Student [name: Tom,sno: 1]
方法2:继承JSONEncoder和JSONDecoder实现子类 import json class MyJSONEncoder(json.JSONEncoder): def default(self,obj): d = {} d['__class__'] = obj.__class__.__name__ d['__module__'] = obj.__module__ d.update(obj.__dict__) return d class MyJSONDecoder(json.JSONDecoder): def __init__(self): json.JSONDecoder.__init__(self,object_hook=self.dict2obj) def dict2obj(self,d): if '__class__' in d: class_name = d.pop('__class__') module_name = d.pop('__module__') module = __import__(module_name) class_ = getattr(module,class_name) args = dict((key.encode('ascii'),value in d.items()) instance = class_(**args) else: instance = d return instance 序列化测试: >>> stu = Student('Tom',1) # 方式一:直接调用子类MyJSONEncoder的encode()方法进行序列化 >>> MyJSONEncoder().encode(stu) '{"__class__": "Student","name": "Tom","sno": 1}' >>> MyJSONEncoder(separators=(',':')).encode(stu) '{"__class__":"Student","__module__":"__main__","name":"Tom","age":19,"sno":1}' # 方式二:将子类MyJSONEncoder作为cls参数的值传递给json.dumps()函数 >>> json.dumps(stu,cls=MyJSONEncoder) '{"__class__": "Student","sno": 1}' >>> json.dumps(stu,cls=MyJSONEncoder,':')) '{"__class__":"Student","sno":1}' 反序列化测试: >>> MyJSONDecoder().decode('{"sno": 1,"name": "Tom"}') Student [name: Tom,sno: 1] 说明: 经过测试发现 三、pickle模块 pickle模块实现了用于对Python对象结构进行 序列化 和 反序列化 的二进制协议,与json模块不同的是pickle模块序列化和反序列化的过程分别叫做 pickling 和 unpickling:
1. pickle模块与json模块对比
2. pickle模块使用的数据流格式 上面提到,pickle使用的数据格式是特定于Python的。这使得它不受诸如JSON或XDR的外部标准限值,但是这也意味着非Python程序可能无法重建pickled Python对象。默认情况下,pickle数据格式使用相对紧凑的二进制表示。如果需要最佳大小特征,可以有效的压缩pickled数据。pickletools模块包含可以用于对pickle生成的数据流进行分析的工具。目前有5种不同的协议可以用于pickle。使用的协议越高,就需要更新的Python版本去读取pickle产生的数据:
说明: Python 2.x中默认使用的是协议v0,如果协议指定为赋值或HIGHEST_PROTOCOL,将使用当前可用的最高协议版本;Python 3.x中默认使用的是协议v3,它兼容其他Python 3版本,但是不兼容Python 2。 注意: 序列化(Serialization)是一个比持久化(Persistence)更加原始的概念;虽然 3. pickle模块提供的相关函数 pickle模块提供的几个序列化/反序列化的函数与json模块基本一致: # 将指定的Python对象通过pickle序列化作为bytes对象返回,而不是将其写入文件 dumps(obj,protocol=None,*,fix_imports=True) # 将通过pickle序列化后得到的字节对象进行反序列化,转换为Python对象并返回 loads(bytes_object,fix_imports=True,encoding="ASCII",errors="strict") # 将指定的Python对象通过pickle序列化后写入打开的文件对象中,等价于`Pickler(file,protocol).dump(obj)` dump(obj,file,fix_imports=True) # 从打开的文件对象中读取pickled对象表现形式并返回通过pickle反序列化后得到的Python对象 load(file,errors="strict") 说明: 上面这几个方法参数中,*号后面的参数都是Python 3.x新增的,目的是为了兼容Python 2.x,具体用法请参看官方文档。 4. 实例:内置数据类型的序列化/反序列化 Python 2.x >>> import pickle >>> >>> var_a = {'a':'str',6)} # 序列化 >>> var_b = pickle.dumps(var_a) >>> var_b "(dp0nS'a'np1nS'str'np2nsS'c'np3nI01nsS'b'np4nF11.1nsS'e'np5nI10nsS'd'np6nNsS'g'np7n(I4nI5nI6ntp8nsS'f'np9n(lp10nI1naI2naI3nas." # 反序列化 >>> var_c = pickle.loads(var_b) >>> var_c {'a': 'str','g': (4,6),3]} Python 3.x >>> import pickle >>> >>> var_a = {'a':'str',6)} # 序列化 >>> var_b = pickle.dumps(var_a) >>> var_b b'x80x03}qx00(Xx01x00x00x00eqx01KnXx01x00x00x00aqx02Xx03x00x00x00strqx03Xx01x00x00x00fqx04]qx05(Kx01Kx02Kx03eXx01x00x00x00gqx06Kx04Kx05Kx06x87qx07Xx01x00x00x00bqx08G@&333333Xx01x00x00x00cqtx88Xx01x00x00x00dqnNu.' # 反序列化 >>> var_c = pickle.loads(var_b) >>> var_c {'e': 10,'d': None} dump()与load() >>> import pickle >>> >>> var_a = {'a':'str',6)} # 持久化到文件 >>> with open('pickle.txt','wb') as f: ... pickle.dump(var_a,f) ... # 从文件中读取数据 >>> with open('pickle.txt','rb') as f: ... var_b = pickle.load(f) ... >>> var_b {'e': 10,'d': None} >>>
说明:
5. 实例:自定义数据类型的序列化/反序列化 首先来自定义一个数据类型: class Student(object): def __init__(self,self.sno) pickle模块可以直接对自定数据类型进行序列化/反序列化操作,无需编写额外的处理函数或类。 >>> stu = Student('Tom',sno: 1] # 序列化 >>> var_b = pickle.dumps(stu) >>> var_b b'x80x03c__main__nStudentnqx00)x81qx01}qx02(Xx04x00x00x00nameqx03Xx03x00x00x00Tomqx04Xx03x00x00x00ageqx05Kx13Xx03x00x00x00snoqx06Kx01ub.' # 反序列化 >>> var_c = pickle.loads(var_b) >>> var_c Student [name: Tom,sno: 1] # 持久化到文件 >>> with open('pickle.txt','wb') as f: ... pickle.dump(stu,f) ... # 从文件总读取数据 >>> with open('pickle.txt','rb') as f: ... pickle.load(f) ... Student [name: Tom,sno: 1] 四、shelve模块 shelve是一个简单的数据存储方案,类似key-value数据库,可以很方便的保存python对象,其内部是通过pickle协议来实现数据序列化。shelve只有一个open()函数,这个函数用于打开指定的文件(一个持久的字典),然后返回一个shelf对象。shelf是一种持久的、类似字典的对象。它与“dbm”的不同之处在于,其values值可以是任意基本Python对象--pickle模块可以处理的任何数据。这包括大多数类实例、递归数据类型和包含很多共享子对象的对象。keys还是普通的字符串。 open(filename,flag='c',writeback=False) flag 参数表示打开数据存储文件的格式,可取值与
protocol 参数表示序列化数据所使用的协议版本,默认是pickle v3; writeback 参数表示是否开启回写功能。 我们可以把shelf对象当dict来使用--存储、更改、查询某个key对应的数据,当操作完成之后,调用shelf对象的close()函数即可。当然,也可以使用上下文管理器(with语句),避免每次都要手动调用close()方法。 实例:内置数据类型操作 # 保存数据 with shelve.open('student') as db: db['name'] = 'Tom' db['age'] = 19 db['hobby'] = ['篮球','看电影','弹吉他'] db['other_info'] = {'sno': 1,'addr': 'xxxx'} # 读取数据 with shelve.open('student') as db: for key,value in db.items(): print(key,': ',value) 输出结果: name : Tom 实例:自定义数据类型操作 # 自定义class class Student(object): def __init__(self,self.sno) # 保存数据 tom = Student('Tom',1) jerry = Student('Jerry',17,2) with shelve.open("stu.db") as db: db['Tom'] = tom db['Jerry'] = jerry # 读取数据 with shelve.open("stu.db") as db: print(db['Tom']) print(db['Jerry']) 输出结果: 五、总结 1. 对比 json模块常用于编写web接口,将Python数据转换为通用的json格式传递给其它系统或客户端;也可以用于将Python数据保存到本地文件中,缺点是明文保存,保密性差。另外,如果需要保存费内置数据类型需要编写额外的转换函数或自定义类。 pickle模块和shelve模块由于使用其特有的序列化协议,其序列化之后的数据只能被Python识别,因此只能用于Python系统内部。另外,Python 2.x 和 Python shelve模块可以看做是pickle模块的升级版,因为shelve使用的就是pickle的序列化协议,但是shelve比pickle提供的操作方式更加简单、方便。shelve模块相对于其它两个模块在将Python数据持久化到本地磁盘时有一个很明显的优点就是,它允许我们可以像操作dict一样操作被序列化的数据,而不必一次性的保存或读取所有数据。 2. 建议
3. 附录
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |