25、序列化与反序列化

Python模块 / 2020-12-11

1、什么是序列化

序列化指的是把内存的数据类型转换成一种特定的格式内容,此格式的内容可用于存储或者跨平台数据交流使用!

反序列化正好相反,从特定格式转化为内存中的数据类型

特定格式即json格式pickle

比如说str把列表字典转换成字符串,也是序列化的概念

2、为何用序列化

用于存储或跨平台平台用!

先说跨平台,公司内部项目也好,开源项目也罢,很多时候都不是仅由一种编程语言开发出来的!下面是我从GitHub上TensorFlow项目中截图下来的:


如上所示,可能会因为各种原因用不同编程语言编写的代码来实现某一项功能!这时候就需要考虑通用性,如何让Python代码也能在Go平台上被识别!这时候需要一种通用格式来兼容各语言,能够相互间转换数据类型!现在比较主流的是json格式!

再来说玩单机游戏,都知道程序是运行在内存中,你的装备等级技能等等都是一个又一个名称所对应的内存地址;当你因为某些事情需要出门时,游戏数据怎么办?下次玩又要重头来吗?这你能忍?我们需要将当前游戏所有状态保存下来到磁盘或者别的地方去,下次玩的时候再加载回来即可,这种我们叫存档!存档只是针对这款游戏或者说针对它后面的程序的,不涉及到什么跨平台等等,因此不需要考虑跨平台兼容性,可以用自己的专有格式,Python中就有pickle,json关注的是跨平台,但是某些语言专有的数据类型它没招,比如Python的集合Java上就没有对应的类型!因此pickle仅适用于Python平台序列化反序列化!

下面来个Python与json对应关系表

PythonJSON
dict{} 即object
list, tuple[] 即array
strstring
int, floatnumber
True, Flasetrue, flase
Nonenull

3、如何用它们?

3.1、json.dumps序列化

调用json.dumps功能转换为json后,返回的是str。

>>> var = json.dumps(['sanxi', 666, True, {'name':'sanxi'}])
>>> print(var,type(var))
["sanxi", 666, true, {"name": "sanxi"}] <class 'str'>
3.2、json.loads反序列化
>>> var = json.loads(var)
>>> print(var, type(var))
['sanxi', 666, True, {'name': 'sanxi'}] <class 'list'>
3.3、序列化结果到文件

其实dump就是调用一个dumps()将数据转换为json格式,再调用write()将其写入目标文件。

var1 = ['sanxi', 666, True, {'name': 'sanxi'}]
with open('hihi.py', 'wt', encoding='utf-8') as json_file:
    json.dump(var1, json_file)
3.4、反序列化文件内容

同上,load先调用read()将文件内容读取出来,再调用loads将其还原为Python数据类型。

with open('hihi.py', 'rt', encoding='utf-8') as load_json:
    print(json.load(load_json))

# 执行得到结果
['sanxi', 666, True, {'name': 'sanxi'}]
3.5、json注意事项
  • json格式是为了兼容所有编程语言通用的数据类型,不能识别某语言专有数据类型,比如Python的集合!
  • json字符串必须用双引号,没有单引号!
  • json.loads可以转化bytes类型,除了Python3.5!
  • 中文序列化为json格式时,是被保存成Unicode格式而不是直观的中文字符
>>> var1 = json.dumps('你好')
>>> print(var1)
"\u4f60\u597d"
>>> json.loads(var1)
'你好'

4、pickle的序列化与反序列化

用法与json模块一致,但是因为局限于Python,且不同版本间可能存在不兼容情况,因此很少用到!

4.1、pickle与json区别

json转换数据类型为str,而pickle转换数据为bytes

4.2、序列化
>>> var2 = pickle.dumps(666)
>>> print(var2, type(var2))
b'\x80\x04\x95\x04\x00\x00\x00\x00\x00\x00\x00M\x9a\x02.' <class 'bytes'>
4.3、反序列化
>>> print(pickle.loads(var2))
666
4.4、序列化到文件

pickle序列化以后存入文件的内容并不能看到,只能使用pickle反序列化以后,才能进行打印显示!

var2 = [666, False, None]
with open('hihi.py', 'wb') as load_pickle:
    pickle.dump(var2, load_pickle)
4.5、反序列化文件内容
with open('hihi.py', 'rb') as load_pickle:
    print(pickle.load(load_pickle))
    
# 执行得到结果
[666, False, None]
4.6、pickle的Python2、3兼容问题

pickle在Python3默认protocol为4,而Python2只支持2,所以用Python3写的代码若需要兼容Python,需要手动指定protocol为2

with open('hihi.py', 'wb') as load_pickle:
    print(pickle.dump('nihao', load_pickle, protocol=2))

5、猴子补丁

monkey patch,由来网上有多个版本,不在本次讨论范畴!实际作用就是在模块运行时替换掉模块源代码,怎么到的呢?在模块被导入时产生了名称空间,通过别名方式篡改名称指向另外的函数,这样子当代码运行时,看似是原来的样子,实际上运行的是另外一个模块的代码!说这有点绕,还是用示例来说明!

之前我们说过在import导入时,名称空间就已经定义好了,所以像import ujson as json这种方式是行不通的,因为这是两个不同的空间!

但是我们导入后通过调用json模块具体的功能再赋予别名可就不一样了,这是可以影响到的,而且Python模块特点是当第一次导完后产生名称空间,后续的导入将直接使用第一次的名称空间,这样后续所有用到json模块指定功能的地方都将直接使用被偷梁换柱的ujson!

# 代码拷贝自网络
import json
import ujson

def monkey_patch_json():
    json.__name__ = ujson.__name__
    json.dump = ujson.dump
    json.load = json.load
    
    
monkey_patch_json()
世间微尘里 独爱茶酒中