Python 序列化与反序列化
1、为什么要序列化
内存中的字典、列表、集合以及各种对象,如何保存到一个文件中?如果是自己定义的类的实例,如何保存到一个文件中?
如何从文件中读取数据,并让它们在内存中再次恢复成自己对应的类的实例?
要设计一套协议,按照某种规则,把内存中数据保存到文件中。文件是一个字节序列,所以必须把数据转换成字节序列,输出到文件。这就是序列化。反之,从文件的字节序列恢复到内存并且还是原来的类型,就是反序列化。
2、定义
serialization 序列化,将内存中结构、对象等存储至磁盘,把它变成一个个字节 ——>
二进制。
deserialization 反序列化,将文件的一个个字节恢复成内存中对象 <——
二进制。
序列化保存到文件就是持久化,可以将数据序列化后持久化,或者网络传输,也可以将从文件中或者网络接收到的字节序列反序列化。Python 提供了 pickle 序列化和反序列化库。
3、pickle库
Python 中的序列化、反序列化模块。
实例:
import pickle filename = "F:/demo/ser" # 序列化后看到什么? a = 97 # 0x61 b = 99 # 0x63 c = 'c' # 0x63 ——> 'c'和99的十六进制一样,序列化如何保存数据类型? # 序列化会将数据类型、数据、边界等等都进行序列化保存,以便反序列化能够正常读取 d = [48, 'abc'] # 0x30 0x61 0x62 0x63 e = {'aaaaa': 'ddd', 'bbbbb': 1, 'ccccc': 0, 'eeeee':'11111'} # 序列化 with open(filename, 'wb') as f: pickle.dump(a, f) # 将对象序列化到文件 pickle.dump(b, f) pickle.dump(c, f) pickle.dump(d, f) pickle.dump(e, f) # 查看本文文件和二进制文件发现:序列化会将数据类型、数据、边界等等都进行序列化保存 # 反序列化 with open(filename, 'rb') as f: # 序列化几次就要反序列化几次 for i in range(5): x = pickle.load(f) print(type(x), x) if isinstance(x, list): print(id(x), id(d)) # id不一样,序列化和反序列化前后不是同一个数据 # 执行结果: <class 'int'> 97 <class 'int'> 99 <class 'str'> c <class 'list'> [48, 'abc'] 2553352971016 2553351787720 <class 'dict'> {'aaaaa': 'ddd', 'bbbbb': 1, 'ccccc': 0, 'eeeee': '11111'}
import pickle # 对象序列化 class AAAA: bbbb = 'ABC' def cccc(self): print('dddd') a1 = AAAA() # 创建AAAA类的对象 # 序列化 ser = pickle.dumps(a1) # 将对象序列化成bytes对象 print("ser={}".format(ser)) # ser=b'\x80\x03c__main__\nAAAA\nq\x00)\x81q\x01.' # 反序列化 a2 = pickle.loads(ser) print(a2.bbbb) # ABC,反序列化后调用 a2.cccc() # dddd,反序列化后调用
上面的例子中,故意使用了连续的 AAAA、ABC、dddd 等字符串,就是为了在二进制文件中能容易的发现它们。
上例中,其实就保存了一个类名,因为所有的其他东西都是类定义的东西,是不变的,所以只序列化一个 AAAA 类名。反序列化的时候找到类就可以恢复一个对象。
import pickle # 对象序列化 class AAAA: def __init__(self): self.bbbb = 'abc' a1 = AAAA() # 创建AAAA类的对象 # 序列化 ser = pickle.dumps(a1) # 将对象序列化成bytes对象 print("ser={}".format(ser)) # ser=b'\x80\x03c__main__\nAAAA\nq\x00)\x81q\x01}q\x02X\x04\x00\x00\x00bbbbq\x03X\x03\x00\x00\x00abcq\x04sb.' # 反序列化 a2 = pickle.loads(ser) print(type(a2), a2) # <class '__main__.AAAA'> <__main__.AAAA object at 0x0000019BE8C1F948> print(a2.bbbb) # abc print(id(a1), id(a2)) # 1769136583432 1769136585032
可以看出这回除了必须保存的 AAAA,还序列化了 bbbb 和 abc,因为这是每一个对象自己的属性,每一个对象不一样的,所以这些数据需要序列化。
3.1 序列化、反序列化实验
定义类 AAA,并序列化到文件:
import pickle # 实验 # 对象序列化 class AAA: def __init__(self): self.aaaa = 'abc' a1 = AAA() # 创建AAA类的对象 # 序列化 ser = pickle.dumps(a1) print("ser={}".format(ser)) # ser=b'\x80\x03c__main__\nAAA\nq\x00)\x81q\x01}q\x02X\x04\x00\x00\x00aaaaq\x03X\x03\x00\x00\x00abcq\x04sb.' print("序列化后的字节长度:{}".format(len(ser))) # 序列化后的字节长度:49 filename = "F:/ser" with open(filename, 'wb') as f: pickle.dump(a1, f)
将产生的序列化文件 ser 发送到其他节点上。
增加一个 x.py 文件,内容如下。最后执行这个脚本:
import pickle with open('ser', 'rb') as f: a = pickle.load(f) # 异常!
会抛出异常 AttributeError: Can't get attribute 'AAA' on <module '__main_' from 't.py'>
。
这个异常实际上是找不到类 AAA 的定义,增加类定义即可解决。
反序列化的时候要找到 AAA 类的定义,才能成功。否则就会抛出异常。
可以这样理解:反序列化的时候,类是模子,二进制序列就是铁水。
import pickle class AAA: def show(self): print('xyz') with open('F:/ser', 'rb') as f: a = pickle.load(f) print(a) # <__main__.AAA object at 0x0000025B9669F2C8> a.show() # xyz print(a.aaaa) # abc
这里定义了类 AAA,并且上面的代码也能成功的执行。
注意:这里的 AAA 定义和原来完全不同了。
因此,序列化、反序列化必须保证使用同一套类的定义,否则会带来不可预料的结果。
4、序列化应用
一般来说,本地序列化的情况,应用较少。大多数场景都应用在网络传输中。
将数据序列化后通过网络传输到远程节点,远程服务器上的服务将接收到的数据反序列化后,就可以使用了。但是,要注意一点,远程接收端,反序列化时必须有对应的数据类型,否则就会报错。尤其是自定义类,必须远程得有一致的定义。
现在,大多数项目,都不是单机的,也不是单服务的,需要多个程序之间配合。需要通过网络将数据传送到其他节点上去,这就需要大量的序列化、反序列化过程。
但是,问题是,Python 程序之间还可以都是用 pickle 解决序列化、反序列化,如果是跨平台、跨语言、跨协议 pickle 就不太适合了,就需要公共的协议。例如 XML、Json、Protocol Buffer 等。
不同的协议,效率不同、学习曲线不同,适用不同场景,要根据不同的情况分析选型。
5、Json
JSON(JavaScript Object Notation, JS对象标记)是一种轻量级的数据交换格式。它基于 ECMAScript(w3c组织制定的 JS 规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。
5.1 Json 的数据类型
双引号引起来的字符串,数值,true,false,null,对象,数组,这些都是值。
字符串
由双引号包围起来的任意字符的组合,可以有转义字符。
数值
有正负,有整数、浮点数。
对象
无序的键值对的集合,key 必须是一个字符串,需要双引号包围这个字符串,value 可以是任意合法的值。
数组
有序的值的集合。
实例:
{ "person": [ { "name": "Tom", "age": 18 }, { "name": "Jerry", "age": 17 } ], "total": 2 }
5.2 json 模块
5.2.1 Python 与 Json
Python 支持少量内建数据类型到 Json 类型的转换。
5.2.2 常用方法
import json d = {'a': 1, 'b': 'abc', 'c': ['1', '2'], 'd': {'e': 50, 'f':(1, 2, 3)}} j = json.dumps(d) print(type(d), type(j)) # <class 'dict'> <class 'str'> # 单引号转成了json要求的双引号,元组被认为是列表,set不能json序列化会报错! print(j) # {"a": 1, "b": "abc", "c": ["1", "2"], "d": {"e": 50, "f": [1, 2, 3]}}
一般 json 编码的数据很少落地,数据都是通过网络传输。传输的时候,要考虑压缩它。本质上来说它就是个文本,就是个字符串。
json 很简单,几乎编程语言都支持 Json,所以应用范围十分广泛。
5.3 simplejson 模块
第三方模块,需要下载:pip install simplejson
import json import simplejson d = {'class1': {'张三': 12, '李四': 13}, 'class2': {'王五': 15, '赵六': 12}, 'class3': {'孙七': 14, '胡八': 13}} j = json.dumps(d) print(type(j), j) s = simplejson.dumps(d) print(type(s), s)
6、MessagePack
# 需要安装 pip install msgpack
MessagePack 是一个基于二进制高效的对象序列化类库,可用于跨语言通信。
它可以像 JSON 那样,在许多种语言之间交换结构对象。
但是它比 JSON 更快速也更轻巧。
支持Python、Ruby、Java、C/C++ 等众多语言。宣称比 Google Protocol Buffers 还要快4倍。兼容 json 和 pickle。
import pickle import json import msgpack d = {'a': 1, 'b': 'abc', 'c': ['1', '2'], 'd': {'e': 50, 'f':(1, 2, 3)}} p = pickle.dumps(d) print(len(p)) # 105 print(p) j = json.dumps(d) print(len(j)) # 69 print(j) m = msgpack.dumps(d) print(len(m)) # 29 print(m) # 执行结果: 105 b'\x80\x03}q\x00(X\x01\x00\x00\x00aq\x01K\x01X\x01\x00\x00\x00bq\x02X\x03\x00\x00\x00abcq\x03X\x01\x00\x00\x00cq\x04]q\x05(X\x01\x00\x00\x001q\x06X\x01\x00\x00\x002q\x07eX\x01\x00\x00\x00dq\x08}q\t(X\x01\x00\x00\x00eq\nK2X\x01\x00\x00\x00fq\x0bK\x01K\x02K\x03\x87q\x0cuu.' 69 {"a": 1, "b": "abc", "c": ["1", "2"], "d": {"e": 50, "f": [1, 2, 3]}} 29 b'\x84\xa1a\x01\xa1b\xa3abc\xa1c\x92\xa11\xa12\xa1d\x82\xa1e2\xa1f\x93\x01\x02\x03'
可以看出,大大的节约了空间。
6.1 常用方法
packb 序列化对象。提供了 dumps 来兼容 pickle 和 json。
unpackb 反序列化对象。提供了 loads 来兼容。
pack 序列化对象保存到文件对象。提供了 dump 来兼容。
unpack 反序列化对象保存到文件对象。提供了 load 来兼容。
实例:
import msgpack d = {'a': 1, 'b': 'abc', 'c': ['1', '2'], 'd': {'e': 50, 'f':(1, 2, 3)}} m1 = msgpack.packb(d) m2 = msgpack.dumps(d) print(m1, m2, sep='\n') m11 = msgpack.unpackb(m1) m22 = msgpack.loads(m2) print(m11, m22, sep='\n') # 执行结果: b'\x84\xa1a\x01\xa1b\xa3abc\xa1c\x92\xa11\xa12\xa1d\x82\xa1e2\xa1f\x93\x01\x02\x03' b'\x84\xa1a\x01\xa1b\xa3abc\xa1c\x92\xa11\xa12\xa1d\x82\xa1e2\xa1f\x93\x01\x02\x03' {'a': 1, 'b': 'abc', 'c': ['1', '2'], 'd': {'e': 50, 'f': [1, 2, 3]}} {'a': 1, 'b': 'abc', 'c': ['1', '2'], 'd': {'e': 50, 'f': [1, 2, 3]}}
MessagePack 简单易用,高效压缩,支持语言丰富。
所以,用它序列化也是一种很好的选择。Python 很多大名鼎鼎的库都是用了 msgpack。