原本要查一下json系列化自定义对象的一个问题,而后发现这篇博客(https://www.cnblogs.com/yyds/p/6563608.html)很全面,感谢做者,关于python序列化的知识点我也学的七七八八了,里面提到了一些我以前感到模糊的地方,看完后以为云雾慢慢散开了,而后就转载了这篇博客来作个总结。html
每种编程语言都有各自的数据类型,其中面向对象的编程语言还容许开发者自定义数据类型(如:自定义类),Python也是同样。不少时候咱们会有这样的需求:python
若是要将一个系统内的数据经过网络传输给其它系统或客户端,咱们一般都须要先把这些数据转化为字符串或字节串,并且须要规定一种统一的数据格式才能让数据接收端正确解析并理解这些数据的含义。XML 是早期被普遍使用的数据交换格式,在早期的系统集成论文中常常能够看到它的身影;现在你们使用更多的数据交换格式是JSON(JavaScript Object Notation),它是一种轻量级的数据交换格式。JSON相对于XML而言,更加加单、易于阅读和编写,同时也易于机器解析和生成。除此以外,咱们也能够自定义内部使用的数据交换格式。web
若是是想把数据持久化到本地磁盘,这部分数据一般只是供系统内部使用,所以数据转换协议以及转换后的数据格式也就不要求是标准、统一的,只要本系统内部可以正确识别便可。可是,系统内部的转换协议一般会随着编程语言版本的升级而发生变化(改进算法、提升效率),所以一般会涉及转换协议与编程语言的版本兼容问题,下面要介绍的pickle协议就是这样一个例子。算法
将对象转换为可经过网络传输或能够存储到本地磁盘的数据格式(如:XML、JSON或特定格式的字节串)的过程称为序列化;反之,则称为反序列化。数据库
本节要介绍的就是Python内置的几个用于进行数据序列化的模块:编程
大部分编程语言都会提供处理json数据的接口,Python 2.6开始加入了json模块,且把它做为一个内置模块提供,无需下载便可使用。json
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, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)
除此以外,json模块还提供了两个额外的方法容许咱们直接将序列化后获得的json数据保存到文件中,以及直接读取文件中的json数据进行反序列化操做:socket
序列化:将Python对象转换成json字符串并存储到文件中 dump(obj, fp, 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对象 load(fp, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)
JSON转Python
说明: •Python dict中的非字符串key被转换成JSON字符串时都会被转换为小写字符串; •Python中的tuple,在序列化时会被转换为array,可是反序列化时,array会被转化为list; •由以上两点可知,当Python对象中包含tuple数据或者包含dict,且dict中存在非字符串的key时,反序列化后获得的结果与原来的Python对象是不一致的; •对于Python内置的数据类型(如:str, unicode, int, float, bool, None, list, tuple, dict)json模块能够直接进行序列化/反序列化处理;对于自定义类的对象进行序列化和反序列化时,须要咱们本身定义一个方法来完成定义object和dict之间进行转化。
# 序列化 >>> 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, 5, 6], "f": [1, 2, 3]}'
sort_keys参数: 表示序列化时是否对dict的key进行排序(dict默认是无序的)
# 序列化并对key进行排序 >>> json.dumps({'a':'str', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 6)}, sort_keys=True) '{"a": "str", "b": 11.1, "c": true, "d": null, "e": 10, "f": [1, 2, 3], "g": [4, 5, 6]}'
indent参数: 表示缩进的意思,它可使得数据存储的格式变得更加优雅、可读性更强;若是indent是一个非负整数或字符串,则JSON array元素和object成员将会被以相应的缩进级别进行打印输出;若是indent是0或负数或空字符串,则将只会插入换行,不会有缩进。
# 序列化并对key进行排序及格式化输出 >>> print(json.dumps({'a':'str', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 6)}, sort_keys=True, indent=4)) { "a": "str", "b": 11.1, "c": true, "d": null, "e": 10, "f": [ 1, 2, 3 ], "g": [ 4, 5, 6 ] }
separators参数: 尽管indent参数可使得数据存储的格式变得更加优雅、可读性更强,可是那是经过添加一些冗余的空白字符进行填充的。当json被用于网络数据通讯时,应该尽量的减小无用的数据传输,这样能够节省带宽并加快数据传输速度。json模块序列化Python对象后获得的json字符串中的','号和':'号分隔符后默认都会附加一个空白字符,咱们能够经过separators参数从新指定分隔符,从而去除无用的空白字符;
>>> 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, 5, 6], "f": [1, 2, 3]}' >>> json.dumps({'a':'str', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 6)}, separators=(',',':')) '{"a":"str","c":true,"b":11.1,"e":10,"d":null,"g":[4,5,6],"f":[1,2,3]}'
ensure_ascii参数: 当该参数的值为True(默认值)时,输出中的全部非ASCII字符(好比中文)都会被转义成'\uXXXX'组成的序列,获得的结果是一个彻底由ASCII字符组成的str实例。若是咱们想获得一我的类可读的输出结果,须要把ensure_ascii参数的值设置为False。
>>> stu={"name": "小明", "age" : 16} >>> stu_json = json.dumps(stu) >>> print(stu_json) '{"name": "\u5c0f\u660e", "age": 16}' >>> stu_json01 = json.dumps(stu, ensure_ascii=False) >>> print(stu_json01) '{"name": "小明", "age": 16}'
说明: 实际上'\uXXXX'是Unicode字符对应的内存编码值,该内存编码名称为"unicode-escape",咱们能够经过unicodestr.encode('unicode-escape')
和decode('unicode-escape')
来完成Unicode字符串与Unicode内存编码序列进行相互转换,以下所示:
>>> str1 = "hello 中国" >>> str2 = str1.encode("unicode_escape") >>> print(str2) b'hello \\u4e2d\\u56fd' >>> str3 = str2.decode("unicode_escape") >>> print(str3) hello 中国
注意str2是字节串,不是字符串,所以\u前面须要再加一个反斜线作转义。咱们把str2转换成字符串就是咱们熟悉的格式了:
>>> str4=str2.decode("utf-8") >>> print(str4) hello \u4e2d\u56fd >>>
反序列化
# 反序列化 >>> json.loads('{"a": "str", "c": true, "b": 11.1, "e": 10, "d": null, "g": [4, 5, 6], "f": [1, 2, 3]}') {'c': True, 'e': 10, 'a': 'str', 'g': [4, 5, 6], 'd': None, 'f': [1, 2, 3], 'b': 11.1} >>> json.loads('{"a":"str","c":true,"b":11.1,"e":10,"d":null,"g":[4,5,6],"f":[1,2,3]}') {'c': True, 'e': 10, 'a': 'str', 'g': [4, 5, 6], 'd': None, 'f': [1, 2, 3], 'b': 11.1}
dump()与load()函数示例
# 序列化到文件中 >>> with open('test.json', 'w') as fp: ... json.dump({'a':'str中国', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 6)}, fp, indent=4) # 反序列化文件中的内容 >>> with open('test.json', 'r') as fp: ... json.load(fp) {'e': 10, 'g': [4, 5, 6], 'b': 11.1, 'c': True, 'd': None, 'a': 'str中国', 'f': [1, 2, 3]}
须要说明的是: 若是试图使用相同的fp重复调用dump()函数去序列化多个对象(或序列化同一个对象屡次),将会产生一个无效的JSON文件,也就是说对于一个fp只能调用一次dump()。
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, age: 19, sno: 1] is not JSON serializable
上面的异常信息中指出:stu对象不能够被序列化为JSON格式的数据。那么咱们分别经过“编写转换函数” 和 “继承JSONEncoder和JSONDecoder类” 来实现对这个自定义数据类型的JSON序列化和反序列化。
那么这个转换函数要完成哪两个数据类型之间的转换呢? 从上面列出的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
序列化测试:
>>> 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, "__module__": "__main__", "age": 19, "__class__": "Student", "name": "Tom"}' json.dumps(stu, default=obj2dict) 等价于 json.dumps(obj2dict(stu))
反序列化测试:
>>> json.loads('{"sno": 1, "__module__": "__main__", "age": 19, "__class__": "Student", "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, "__module__": "__main__", "age": 19, "__class__": "Student", "name": "Tom"}')) Student [name: Tom, age: 19, sno: 1] >>> json.loads('{"sno": 1, "__module__": "__main__", "age": 19, "__class__": "Student", "name": "Tom"}', object_hook=dict2obj) Student [name: Tom, age: 19, sno: 1] json.loads(JSON_STR, object_hook=dict2obj) 等价于 dict2obj(json.loads(JSON_STR))
方法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) for key, value in d.items()) instance = class_(**args) else: instance = d return instance
序列化测试:
>>> stu = Student('Tom', 19, 1) # 方式一:直接调用子类MyJSONEncoder的encode()方法进行序列化 >>> MyJSONEncoder().encode(stu) '{"__class__": "Student", "__module__": "__main__", "name": "Tom", "age": 19, "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", "__module__": "__main__", "name": "Tom", "age": 19, "sno": 1}' >>> json.dumps(stu, cls=MyJSONEncoder, separators=(',', ':')) '{"__class__":"Student","__module__":"__main__","name":"Tom","age":19,"sno":1}'
反序列化测试:
>>> MyJSONDecoder().decode('{"sno": 1, "__module__": "__main__", "age": 19, "__class__": "Student", "name": "Tom"}') Student [name: Tom, age: 19, sno: 1]
说明: 通过测试发现
MyJSONDecoder().decode(JSON_STR)
和json.loads(JSON_STR, object_hook=dict2obj)
只能在Python 2.7上正确执行,在Python 3.5上没法正确执行;而json.loads(JSON_STR, cls=MyJSONDecoder)
不管在Python 2.7仍是在Python 3.5上都没法正确执行。这说明json模块对于自定义数据类型的反序列化支持仍是比较有限的,可是咱们也能够经过json.loads(JSON_STR)函数,不指定cls参数来获得一个dict对象,而后本身完成dict到object的转换。
继承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)
pickle模块实现了用于对Python对象结构进行 序列化 和 反序列化 的二进制协议,与json模块不一样的是pickle模块序列化和反序列化的过程分别叫作 pickling 和 unpickling:
上面提到,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)更加原始的概念;虽然
pickle
能够读写文件对象,可是它不处理持久化对象的命名问题,也不处理对持久化对象的并发访问问题(甚至更复杂的问题)。pickle
模块能够将复杂对象转换为字节流,而且能够将字节流转换为具备相同内部结构的对象。或许最可能对这些字节流作的事情是将它们写入文件,可是也能够对它们进行网络传输或将它们存储在数据库中。shelve
模块提供了一个简单的接口用于在DBM风格的数据库文件上对对象进行pickle和unpickle操做。
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, protocol=None, *, fix_imports=True) # 从打开的文件对象中读取pickled对象表现形式并返回经过pickle反序列化后获得的Python对象 load(file, *, fix_imports=True, encoding="ASCII", errors="strict")
说明: 上面这几个方法参数中,*号后面的参数都是Python 3.x新增的,目的是为了兼容Python 2.x,具体用法请参看官方文档。
>>> import pickle >>> >>> var_a = {'a':'str', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 6)} # 序列化 >>> var_b = pickle.dumps(var_a) >>> var_b "(dp0\nS'a'\np1\nS'str'\np2\nsS'c'\np3\nI01\nsS'b'\np4\nF11.1\nsS'e'\np5\nI10\nsS'd'\np6\nNsS'g'\np7\n(I4\nI5\nI6\ntp8\nsS'f'\np9\n(lp10\nI1\naI2\naI3\nas." # 反序列化 >>> var_c = pickle.loads(var_b) >>> var_c {'a': 'str', 'c': True, 'b': 11.1, 'e': 10, 'd': None, 'g': (4, 5, 6), 'f': [1, 2, 3]}
Python 3.x
>>> import pickle >>> >>> var_a = {'a':'str', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 6)} # 序列化 >>> var_b = pickle.dumps(var_a) >>> var_b b'\x80\x03}q\x00(X\x01\x00\x00\x00eq\x01K\nX\x01\x00\x00\x00aq\x02X\x03\x00\x00\x00strq\x03X\x01\x00\x00\x00fq\x04]q\x05(K\x01K\x02K\x03eX\x01\x00\x00\x00gq\x06K\x04K\x05K\x06\x87q\x07X\x01\x00\x00\x00bq\x08G@&333333X\x01\x00\x00\x00cq\t\x88X\x01\x00\x00\x00dq\nNu.' # 反序列化 >>> var_c = pickle.loads(var_b) >>> var_c {'e': 10, 'a': 'str', 'f': [1, 2, 3], 'g': (4, 5, 6), 'b': 11.1, 'c': True, 'd': None}
dump()与load()
>>> import pickle >>> >>> var_a = {'a':'str', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 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, 'a': 'str', 'f': [1, 2, 3], 'g': (4, 5, 6), 'b': 11.1, 'c': True, 'd': None} >>>
说明:
首先来自定义一个数据类型:
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) pickle模块能够直接对自定数据类型进行序列化/反序列化操做,无需编写额外的处理函数或类。 >>> stu = Student('Tom', 19, 1) >>> print(stu) Student [name: Tom, age: 19, sno: 1] # 序列化 >>> var_b = pickle.dumps(stu) >>> var_b b'\x80\x03c__main__\nStudent\nq\x00)\x81q\x01}q\x02(X\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00Tomq\x04X\x03\x00\x00\x00ageq\x05K\x13X\x03\x00\x00\x00snoq\x06K\x01ub.' # 反序列化 >>> var_c = pickle.loads(var_b) >>> var_c Student [name: Tom, age: 19, 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, age: 19, sno: 1]
shelve是一个简单的数据存储方案,相似key-value数据库,能够很方便的保存python对象,其内部是经过pickle协议来实现数据序列化。shelve只有一个open()函数,这个函数用于打开指定的文件(一个持久的字典),而后返回一个shelf对象。shelf是一种持久的、相似字典的对象。它与“dbm”的不一样之处在于,其values值能够是任意基本Python对象--pickle模块能够处理的任何数据。这包括大多数类实例、递归数据类型和包含不少共享子对象的对象。keys仍是普通的字符串。
open(filename, flag='c', protocol=None, writeback=False)
flag 参数表示打开数据存储文件的格式,可取值与dbm.open()
函数一致:
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 age : 19 hobby : ['篮球', '看电影', '弹吉他'] other_info : {'sno': 1, 'addr': 'xxxx'}
实例:自定义数据类型操做
# 自定义class 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) # 保存数据 tom = Student('Tom', 19, 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']) 输出结果: Student [name: Tom, age: 19, sno: 1] Student [name: Jerry, age: 17, sno: 2]
json模块经常使用于编写web接口,将Python数据转换为通用的json格式传递给其它系统或客户端;也能够用于将Python数据保存到本地文件中,缺点是明文保存,保密性差。另外,若是须要保存非内置数据类型须要编写额外的转换函数或自定义类。
pickle模块和shelve模块因为使用其特有的序列化协议,其序列化以后的数据只能被Python识别,所以只能用于Python系统内部。另外,Python 2.x 和 Python
3.x 默认使用的序列化协议也不一样,若是须要互相兼容须要在序列化时经过protocol参数指定协议版本。除了上面这些缺点外,pickle模块和shelve模块相对于json模块的优势在于对于自定义数据类型能够直接序列化和反序列化,不须要编写额外的转换函数或类。
shelve模块能够看作是pickle模块的升级版,由于shelve使用的就是pickle的序列化协议,可是shelve比pickle提供的操做方式更加简单、方便。shelve模块相对于其它两个模块在将Python数据持久化到本地磁盘时有一个很明显的优势就是,它容许咱们能够像操做dict同样操做被序列化的数据,而没必要一次性的保存或读取全部数据。