一、概述
玩过稍微大型一点的游戏的朋友都知道,很多游戏的存档功能使得我们可以方便地迅速进入上一次退出的状态(包括装备、等级、经验值等在内的一切运行时数据),那么在程序开发中也存在这样的需求:比较简单的程序,对象的处理都在内存中直接实现,程序退出后对象就消失;但对于功能需求稍微拔高一点的程序来讲,很多时候往往需要需要把对象持久化保存起来,以便下次启动程序时还能直接进入最后一次的状态。
这个处理过程在程序开发中就是序列化与反序列化。
二、序列化与反序列化的概念
概述中引入了一个游戏存档的场景,本质上是游戏程序把运行时的对象转换成可以持久存储的对象,然后保存(到数据库)的过程。还是以这个为引子来讲讲序列化与反序列化的概念(以下概念整合自网络资料,个人认为解释比较到位了)。
序列化
我们把程序运行时内存中的数据结构或对象转换成二进制串字节序列的过程称之为序列化,这样我们就可以对对象实现持久化存储或网络传输。
请注意以下重点:- 序列化的对象
是内存中的数据结构或对象,也就是我们在程序运行中操纵的一切对象(这是一个面向对象的时代,当然包括很多地方说的变量啦~) - 序列化后的对象
变为二进制串字节序列,这个不作过多解释,要持久化保存到硬件或进行网络传输,必须是bytes对象。 - 序列化的目的
想想自己玩游戏时存档的那种便利性和必要性把,有些对象必须能够持久化地保存(一般文件,数据库,巴拉巴拉…)或进行网络传输(分布式程序),才能满足功能需求。
- 序列化的对象
反序列化
反序列化就是序列化的逆向过程,持久化保存或网络传输数据的最终目的也是为了后续使用,必须可以逆向加载到内存中二次利用,这就是反序列化。
python中的序列化与反序列化模块有json和pickle,下面就来看看怎么玩转它们。
三、json模块
json模块提供了dumps,loads,dump和load四种方法,下面展开来阐述:
1. dumps序列化和loads反序列化
dumps和loads是成对出现的:
dumps用于将python中的简单数据类型(典型的是字典和列表,还有字符串)进行json格式encode编码,转换为符合json格式的字符串(返回标准的json格式字符串);
loads则刚好相反,用于把符合json格式的字符串decode成python中特定的数据类型(注意是简单的数据类型,下文会详细解释)。
>>> import json
>>> list1=['a','b','c']
>>> print(type(list))
<class 'type'>
# dumps序列化,可以理解为encode json过程
>>> print(json.dumps(list1))
["a", "b", "c"]
>>> print(type(json.dumps(list1)))
<class 'str'> # list dumps处理后变为str类型
>>> dict1={'id':'001','name':'Pumpkin'}
>>> print(type(dict1))
<class 'dict'>
>>> print(type(json.dumps(dict1)))
<class 'str'> # dict经过dumps处理后也变成str类型
# loads反序列化
>>> print(json.loads(json.dumps(list1)))
['a', 'b', 'c']
>>> print(json.loads(json.dumps(dict1)))
{'id': '001', 'name': 'Pumpkin'}
>>>print(type(json.loads(json.dumps(list1))))
<class 'list'> # 把经过json dumps处理过的字符串loads序列化,可还原为原来的数据类型
>>> print(type(json.loads(json.dumps(dict1))))
<class 'dict'> # 把经过json dumps处理过的字符串loads序列化,可还原为原来的数据类型
以上代码仅仅是展示loads方法的效果,实际使用中我们可以把符合python中json格式的自定义字符串(比如程序中的输入)转换成特定的数据类型,这样就可以在程序中跑起来:
>>> str1='["a", "b", "c"]'
>>> print(type(json.loads(str1)))
<class 'list'> # 反序列化为list
>>> print(json.loads(str1))
['a', 'b', 'c']
>>> str2='{"id":"001","name":"Pumpkin"}'
>>> print(json.loads(str2))
{'id': '001', 'name': 'Pumpkin'}
>>> print(json.loads(str2))
{'id': '001', 'name': 'Pumpkin'}
>>> print(type(json.loads(str2)))
<class 'dict'> # 反序列化为dict
注意:通过loads方法反序列化自定义的字符串时,外层的引号必须是单引号,内层的引号是双引号(可以对照dumps后输出的引号是双引号来看),这是python的规范,别问为什么了。
好了,以上展示了dumps和loads的处理过程,我们的目的不是把python中的运行时对象能进行持久化保存或网络传输吗?下面展示通过dumps处理后保存到文本文件和从文本文件中loads出历史保存的数据效果:
(1) dumps序列化后保存
import json
dict1 = {'id':'001','name':'Pumpkin'}
with open('dumps.txt', 'w', encoding = 'utf-8') as f:
f.write(json.dumps(dict1))
看看保存后的文本文件dumps.txt的内容:
(2) loads反序列化从文件中加载数据
>>> import json
>>> with open('dumps.txt','r',encoding='utf-8') as f:
... content = f.read()
>>> print(json.loads(content))
{'id': '001', 'name': 'Pumpkin'}
>>> print(type(json.loads(content)))
<class 'dict'> #loads反序列化后成功还原为原来的数据类型dict
>>> print(json.loads(content).get('name'))
Pumpkin #此时可以应用dict的各种大法了
>>> print(json.loads(content)['name'])
Pumpkin
2、 dump序列化和load反序列化
dump和load也是成对出现的,dump可以把python中的特定数据类型(比较多用的还是dict和list)转换为json格式,并直接写入写入一个file-like Object(操作的对象包括未转换的python对象和file-like Object),load则与此相反,可从文件对象中反序列化出python的原生对象出来。
>>> import json
>>> dict1={'id': '001', 'name': 'Pumpkin'}
>>> with open('dump.txt','w',encoding='utf-8') as f:
... json.dump(dict1,f) # dump序列化,直接操作原生数据类型对象和文件句柄
...
#load反序列化
>>> with open('dump.txt','r',encoding='utf-8') as f:
... content = json.load(f) # 注意这里先用一个对象把load的内容保存起来,否则关闭文件后就不能再访问了
...
>>> print(content)
{'id': '001', 'name': 'Pumpkin'}
>>> print(type(content))
<class 'dict'> # 成功反序列化成dict
>>> print(content['name'])
Pumpkin # 试试dict大法
3、(dumps & loads) VS (dump & load)
对比下dumps & loads和dump & load吧:
(1)dumps & loads
只能解决python中可以被json模块处理的对象和json格式字符串之间相互转换的问题,它们操作的对象分别是可以被json模块处理的对象和json格式字符串。
(2)dump和load
可以理解为json文件处理函数,操作的对象是python中可以被json模块处理的对象和file-like Object,屏蔽或者说省略了json格式字符串这一细节。
综合对比起来,如果要通过文件保存或从文件中加载运行时对象,使用dump和load更方便,代码更少;反之如果仅仅需要进行json格式处理,则建议使用dumps和loads。
四、pickle
pickle模块实现了用于对Python对象结构进行序列化和反序列化的二进制协议,与json模块不同的是pickle模块序列化和反序列化的过程分别叫做 pickling 和 unpickling,且转换前后是二进制字节码,不再是简单的可阅读的字符串:
- pickling: 是将Python对象转换为字节流的过程;
- unpickling: 是将字节流二进制文件或字节对象转换回Python对象的过程;
1. dumps序列化和loads反序列化
与json的dumps和loads非常类似,不同的就在于转换后的格式是二进制字节码
>>> import pickle
>>> dict1={'id':'001','name':'Pumpkin'}
>>> pickle.dumps(dict1)
b'\x80\x03}q\x00(X\x02\x00\x00\x00idq\x01X\x03\x00\x00\x00001q\x02X\x04\x00\x00\
x00nameq\x03X\x07\x00\x00\x00Pumpkinq\x04u.' # 序列化成二进制字节码
>>> print(type(pickle.dumps(dict1)))
<class 'bytes'>
>>> pickle.loads(pickle.dumps(dict1)) # 成功反序列化
{'id': '001', 'name': 'Pumpkin'}
>>> print(type(pickle.loads(pickle.dumps(dict1))))
<class 'dict'>
>>> pickle.loads(pickle.dumps(dict1))['name']
'Pumpkin'
由于pickle序列化后数据类型变为二进制字节码,因此在保存文件和读取文件时需要分别以wb和rb模式打开:
>>> import pickle
>>> dict1={'id':'001','name':'Pumpkin'}
>>> with open('picklt.txt','wb') as f: # 以wb模式打开文件后写入dumps内容
... f.write(pickle.dumps(dict1))
...
>>> with open('picklt.txt','rb') as f: # 以rb模式打开后读取内容
... data = pickle.loads(f.read())
...
>>> print(data)
{'id': '001', 'name': 'Pumpkin'}
通过dumps写入后,由于是二进制字节码,所以打开picklt.txt时会有乱码显示.
2. dump序列化和load反序列化
同理,pickle的dump序列化和load反序列化也和json的dump、load非常类似,还是看相同的例子吧:
>>> import pickle
>>> dict1={'id': '001', 'name': 'Pumpkin'}
>>> with open('pickle.txt','wb') as f:
... pickle.dump(dict1,f)
...
>>> with open('pickle.txt','rb') as f:
... content = pickle.load(f)
...
>>> print(content)
{'id': '001', 'name': 'Pumpkin'}
>>> print(content['name'])
Pumpkin
3. 序列化函数
(1)序列化
# !/usr/bin/env python
# -*- coding: utf-8 -*-
import pickle
def sayhi(name):
print('Hello:', name)
info = {'name':'Pumpkin', 'func':sayhi} #func对应的值是一个函数
with open('test.txt', 'wb') as f:
data = pickle.dumps(info)
f.write(data)
(2)反序列化
# -*- coding: utf-8 -*-
import pickle
def sayhi(name): #此处需要定义出函数,因为它不能被直接加载到内存中
print('Hello:',name)
with open('test.txt','rb') as f:
data = pickle.loads(f.read())
print(data.get('name'))
data.get('func')('Tom')
结果输出:
Pumpkin
Hello: Tom
四、json和pickle对比
JSON是一种文本序列化格式(它输出的是unicode文件,大多数时候会被编码为utf-8),而pickle是一个二进制序列化格式;
JOSN处理的是python对象和字符串的转换问题,是我们可以读懂的数据格式,而pickle是二进制格式,我们无法读懂;
JSON是与特定的编程语言或系统无关的,且它在Python生态系统之外被广泛使用,而pickle使用的数据格式是特定于Python的;
默认情况下,JSON只能表示Python内建数据类型,而且是仅限于比较简单的数据类型,如dict,list和str,对于自定义数据类型需要一些额外的工作来完成;pickle可以直接表示大量的Python数据类型,包括自定数据类型(其中,许多是通过巧妙地使用Python内省功能自动实现的;复杂的情况可以通过实现specific object API来解决)。