# 40. 序列化模块,json、pickle、shelve
# 序列化模块
序列化分为二部分,序列化(正序列化)、反序列化
- 序列化(正序列化):将原本的字典、列表等一些类型的内容转成一个字符串的过程
- 反序列化:将转成的序列化字符串转换回原本的字典、列表等一些类型的内容的过程
# 那为什么要有序列化模块
比如,我们在python代码中计算的一个数据需要给另外一段程序使用,那我们怎么给?
现在我们能想到的方法就是存在文件里,然后另一个python程序再从文件里读出来。
但是我们都知道,对于文件来说是没有字典这个概念的,所以我们只能将数据转换成字典放到文件中。
你一定会问,将字典转换成一个字符串很简单,就是str(dic)就可以办到了,为什么我们还要学习序列化模块呢?
没错序列化的过程就是从dic 变成str(dic)的过程。现在你可以通过str(dic),将一个名为dic的字典转换成一个字符串,
但是你要怎么把一个字符串转换成字典呢?
聪明的你肯定想到了eval(),如果我们将一个字符串类型的字典str_dic传给eval,就会得到一个返回的字典类型了。
eval()函数十分强大,但是eval是做什么的?
python官方demo解释为:将字符串str当成有效的表达式来求值并返回计算结果。
BUT!强大的函数有代价。安全性是其最大的缺点。
想象一下,如果我们从文件中读出的不是一个数据结构,而是一句"删除文件"类似的破坏性语句,那么后果实在不堪设设想。
而使用eval就要担这个风险。
所以,我们并不推荐用eval方法来进行反序列化操作(将str转换成python中的数据结构)
# 序列化的目的
- 以某种存储形式使自定义对象持久化(持久化:将其写入磁盘、数据库等永久存储的方式)
- 将对象从一个地方传递到另一个地方
- 使程度更具维护性
- 序列化都是,数据转成序列化(字符串),序列化(字符串)转成数据
# 序列化的三大模块
json模块
- 支持所有语言,拥有跨语言的优势
- 支持的数据类型有限
- 限制比较多
pickle模块
- 序列化后的数据都是bytes类型,想要查看就只能反序列后在看
- 支持几乎所有对象的序列化
shelve模块
- 不常用
- 在3.x 版本的Python,shelve只读选项不支持,目前是否支持不清楚
- 需要大量范围的读取类字典格式的程序,才推荐使用
- 一般推荐使用json模块跟pickle模块
# json模块
json一共只提供四个方法:dumps、dump、losds、load
注意:json转换完的字符串类型都是用 " " 来表示
# dumps序列化方法
注意:json转换完的字符串类型都是用 " " 来表示
import json
so = {1:'a',2:'b',3:'c',4:'e',5:'f',6:'g'}
print(json.dumps(so))
print([so])
print([json.dumps(so)])
执行结果:
{"1": "a", "2": "b", "3": "c", "4": "e", "5": "f", "6": "g"}
[{1: 'a', 2: 'b', 3: 'c', 4: 'e', 5: 'f', 6: 'g'}]
['{"1": "a", "2": "b", "3": "c", "4": "e", "5": "f", "6": "g"}']
## 从执行结果就能看出来区别了吧
# loads反序列化方法
注意:字典的key值转成序列化前都是int类型,转后就成字符串类型,在反序列转后就还是字符串,只是 "" 换成了 ''
import json
so = {1:'a',2:'b',3:'c',4:'e',5:'f',6:'g'}
so = json.dumps(so)
print(json.loads(so))
print([json.loads(so)])
执行结果:
{'1': 'a', '2': 'b', '3': 'c', '4': 'e', '5': 'f', '6': 'g'}
[{'1': 'a', '2': 'b', '3': 'c', '4': 'e', '5': 'f', '6': 'g'}]
# dump用于传递的序列化方法
注意:json转换完的字符串类型都是用 " " 来表示
格式:json.dump(要转换的数据,要传递的方式)
import json
so = {1:'a',2:'b',3:'c',4:'e',5:'f',6:'g'}
with open("json_so",mode="w",encoding="utf-8") as f:
json.dump(so,f)
## 执行后文件内容
{"1": "a", "2": "b", "3": "c", "4": "e", "5": "f", "6": "g"}
# load用于传递的反序列化方法
注意:字典的key值转成序列化前都是int类型,转后就成字符串类型,在反序列转后就还是字符串,只是 "" 换成了 ''
格式:json.dump(要传递的方式)
import json
so = {1:'a',2:'b',3:'c',4:'e',5:'f',6:'g'}
with open("json_so",mode="r",encoding="utf-8") as f:
so = json.load(f)
print(so)
print([so])
print(type(so))
执行结果:
{'1': 'a', '2': 'b', '3': 'c', '4': 'e', '5': 'f', '6': 'g'}
[{'1': 'a', '2': 'b', '3': 'c', '4': 'e', '5': 'f', '6': 'g'}]
<class 'dict'>
# json序列化选项
序列化选项,分常用跟不常用,常用只有一个,哈哈哈哈哈哈
# 常用的序列化选项 - ensure_ascii
ensure_ascii:
- True:默认就是True,是否对中文进行编译,默认是
- False:不对中文进行编译
so = {1:'a',2:'b',3:'c',4:'e',5:'f',6:'g',7:"中国"}
print(json.dumps(so))
print(json.dumps(so,ensure_ascii=False))
执行结果:
{"1": "a", "2": "b", "3": "c", "4": "e", "5": "f", "6": "g", "7": "\u4e2d\u56fd"}
{"1": "a", "2": "b", "3": "c", "4": "e", "5": "f", "6": "g", "7": "中国"}
# 其他不常用的选项
- Skipkeys:默认值是False,如果dict的keys内的数据不是python的基本类型(str,unicode,int,long,float,bool,None),设置为False时,就会报TypeError的错误。此时设置成True,则会跳过这类key
- ensure_ascii:,当它为True的时候,所有非ASCII码字符显示为\uXXXX序列,只需在dump时将ensure_ascii设置为False即可,此时存入json的中文即可正常显示。)
- indent:应该是一个非负的整型,如果是0就是顶格分行显示,如果为空就是一行最紧凑显示,否则会换行且按照indent的数值显示前面的空白分行显示,这样打印出来的json数据也叫pretty-printed json
- separators:分隔符,实际上是(item_separator, dict_separator)的一个元组,默认的就是(‘,’,’:’);这表示dictionary内keys之间用“,”隔开,而KEY和value之间用“:”隔开。
- sort_keys:将数据根据keys的值进行排序
import json
data = {'username':['李华','二愣子'],'sex':'male','age':16}
print(json.dumps(data,sort_keys=True,indent=2,separators=(',',':'),ensure_ascii=False))
so = {1:'a',2:'b',3:'c',4:'e',5:'f',6:'g',7:'中国'}
print(json.dumps(so,sort_keys=True,indent=2,separators=(':',','),ensure_ascii=False))
执行结果:
{
"age":16,
"sex":"male",
"username":[
"李华",
"二愣子"
]
}
{
"1","a":
"2","b":
"3","c":
"4","e":
"5","f":
"6","g":
"7","中国"
}
注意:这样虽然比较好看,也比较看得懂,但是这样进行存储,会大量的消耗容量,如这样进行传递,也会大量的消耗网络资源
# json的限制
json格式的限制:
- json格式的key必须是字符串数据类型
- json格式中的字符串只能是 " "
# 如果是数字为key,那么dump之后会强行转成字符串类型
dic = {1:2,3:4}
str_dic = json.dumps(dic)
print(str_dic)
new_dic = json.loads(str_dic)
print(new_dic)
执行结果:
{"1": 2, "3": 4}
{'1': 2, '3': 4}
# json是否支持元组
对元组做value的字典会把元组强制转换成列表,反序列后,强制转换的列表,转换不了元组
注意:json不支持元组做为字典的key值,在正常的环境下元组是可以做为字典的key,但是json不支持
dic = {'abc':(1,2,3)}
str_dic = json.dumps(dic)
print(str_dic)
new_dic = json.loads(str_dic)
print(new_dic)
print(type(new_dic["abc"]))
执行结果:
{"abc": [1, 2, 3]}
{'abc': [1, 2, 3]}
<class 'list'>
# 对于序列化到文件的限制
## 把列表序列化写入文件中
lst = ['aaa',123,'bbb',12.456]
with open('json_demo','w') as f:
json.dump(lst,f)
## 文件的内容
["aaa", 123, "bbb", 12.456]
## 在读取文件内容并反序列
with open('json_demo') as f:
ret = json.load(f)
print(ret)
执行结果:
['aaa', 123, 'bbb', 12.456]
## 那如果文件的内容是这样的呢
['aaa', 123, 'bbb', 12.456]
## 在读取文件内容并反序列
with open('json_demo') as f:
ret = json.load(f)
print(ret)
执行结果:
raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 2 (char 1)
## 那为什么会报错,因为json在反序列的时候不认识 "" 只认识'',如果存在 '' 那么就会报错
# json能多次dump数据到文件里,但是不能load出来了
## 多次向文件写入序列好的数据
dic = {'abc':(1,2,3)}
lst = ['aaa',123,'bbb',12.456]
with open('json_demo','w') as f:
json.dump(lst,f)
json.dump(dic,f)
## 文件内容
["aaa", 123, "bbb", 12.456]{"abc": [1, 2, 3]}
## 向读取文件内容并反序列
with open('json_demo') as f:
ret = json.load(f)
print(ret)
执行结果:
json.decoder.JSONDecodeError: Extra data: line 1 column 28 (char 27)
**注意:**因为在python反序列的时候,只允许一个变量进行反序列,或者只允许一个数据类型的数据进行反序列
# 如果想要json能多次dump数据到文件里,也能load出来了
这里就要用点取巧的方式
## 多次向文件写入序列好的数据
dic = {'abc':(1,2,3)}
lst = ['aaa',123,'bbb',12.456]
with open('json_demo','w') as f:
str_lst = json.dumps(lst)
str_dic = json.dumps(dic)
f.write(str_lst+'\n')
f.write(str_dic+'\n')
## 文件内容
["aaa", 123, "bbb", 12.456]
{"abc": [1, 2, 3]}
## 向读取文件内容并反序列
with open('json_demo') as f:
for line in f:
ret = json.loads(line)
print(ret)
执行结果:
['aaa', 123, 'bbb', 12.456]
{'abc': [1, 2, 3]}
这里用的是,不用dump方式来传递数据,用原始的文件写入功能,写入一行序列化数据后,在写入换行符,在写入一行序列化数据后,在写入换行符等等
这样子,多个序列化都分行存放,那么读取文件的时候,就可以一行一行读取,使用for循环方法,读取一行就反序列化回来,这样就能比较完善的处理需求
# pickle模块
pickle模块有几点是需要注意的
- 序列化后的数据都是bytes类型,想要查看就只能反序列后在看
- 支持几乎所有对象的序列化
- 对于对象的序列化后要进行反序列化,需要这个对象对应的类在内存中
- dump出来的结果是bytes类型的数据,所以要使用dump传递到文件中,那么文件的打开方式应该要用 "wb" 的方式打开,那么load呢,读取文件的方式也应该是 "rb" 的方式读取
- pickle模块序列化后,在反序化列出的结果,跟原先的一模一样
# dumps序列化方法
注意:dumps序列后的数据都是bytes类型的数据
import pickle
so = ([1,2,"33",{"v1":"n1"},"aaa",["sakl","中国"],{"abc","123"},"中国"])
print(pickle.dumps(so))
执行结果:
b'\x80\x03]q\x00(K\x01K\x02X\x02\x00\x00\x0033q\x01}q\x02X\x02\x00\x00\x00v1q\x03X\x02\x00\x00\x00n1q\x04sX\x03\x00\x00\x00aaaq\x05]q\x06(X\x04\x00\x00\x00saklq\x07X\x06\x00\x00\x00\xe4\xb8\xad\xe5\x9b\xbdq\x08ecbuiltins\nset\nq\t]q\n(X\x03\x00\x00\x00abcq\x0bX\x03\x00\x00\x00123q\x0ce\x85q\rRq\x0eh\x08e.'
## dumps跟dump的结果都是bytes类型的数据,没有什么选项能让他转其他类型的数据,只有通过反序列,load跟loads方法
# loads反序列化方法
import pickle
so = ([1,2,"33",{"v1":"n1"},"aaa",["sakl","中国"],{"abc","123"},"中国"])
so = pickle.dumps(so)
print(pickle.loads(so))
执行结果:
[1, 2, '33', {'v1': 'n1'}, 'aaa', ['sakl', '中国'], {'abc', '123'}, '中国']
# 使用对象进行序列化
import pickle
class coso:
def __init__(self,name,age):
self.name = name
self.age = age
so = coso("江凡",22)
du_so = pickle.dumps(so)
lo_so = pickle.loads(du_so)
print(lo_so.name)
print(lo_so.age)
print(pickle.loads(du_so))
print(pickle.loads(du_so).name)
print(pickle.loads(du_so).age)
执行结果:
江凡
22
<__main__.coso object at 0x0000024681AA5EF0>
江凡
22
# 单次dump到文件跟load文件出来
## 注释类
import pickle
# class coso:
# def __init__(self,name,age):
# self.name = name
# self.age = age
so = coso("江凡",22)
with open("pickle_so","wb") as f:
pickle.dump(so,f)
with open("pickle_so","rb") as f:
lo_so = pickle.load(f)
print(lo_so.name)
print(lo_so.age)
执行结果:
NameError: name 'coso' is not defined
## 报找不到这个类
## 不注释类
import pickle
class coso:
def __init__(self,name,age):
self.name = name
self.age = age
so = coso("江凡",22)
with open("pickle_so","wb") as f:
pickle.dump(so,f)
with open("pickle_so","rb") as f:
lo_so = pickle.load(f)
print(lo_so.name)
print(lo_so.age)
执行结果:
江凡
22
# 多次dump到文件跟load文件出来
多次dump到文件很成功,但是load就有点限制
load的方式很像生成器,写一行,读一行
import pickle
class coso:
def __init__(self,name,age):
self.name = name
self.age = age
so1 = coso("江凡",22)
so2 = coso("李城",19)
so3 = coso("小燕子",21)
with open("pickle_so","wb") as f:
pickle.dump(so1, f)
pickle.dump(so2, f)
pickle.dump(so3, f)
with open("pickle_so","rb") as f:
print(pickle.load(f).name)
print(pickle.load(f).name)
print(pickle.load(f).name)
执行结果:
江凡
李城
小燕子
解决多次dump,load的限制**
import pickle
class coso:
def __init__(self,name,age):
self.name = name
self.age = age
so1 = coso("江凡",22)
so2 = coso("李城",19)
so3 = coso("小燕子",21)
with open("pickle_so","wb") as f:
pickle.dump(so1, f)
pickle.dump(so2, f)
pickle.dump(so3, f)
with open("pickle_so","rb") as f:
while 1:
try:
print(pickle.load(f).name)
except EOFError:
break
执行结果:
江凡
李城
小燕子
## 通过while无限循环,不断的取值,在使用异常处理,如果报EOFError就退出循环
## 那么EOFError错是怎么来的
import pickle
class coso:
def __init__(self,name,age):
self.name = name
self.age = age
so1 = coso("江凡",22)
so2 = coso("李城",19)
so3 = coso("小燕子",21)
with open("pickle_so","wb") as f:
pickle.dump(so1, f)
pickle.dump(so2, f)
pickle.dump(so3, f)
with open("pickle_so","rb") as f:
while 1:
print(pickle.load(f).name)
执行结果:
江凡
李城
小燕子
EOFError: Ran out of input
## 报错就是项就是这样来的
# dump跟load的用法
这里只介绍pickle模块中的dump跟load的一种用法
## 第一步
import pickle
class coso:
def __init__(self,name,age):
self.name = name
self.age = age
so = coso("江凡",22)
so.name = "李城"
with open("pickle_so","wb") as f:
pickle.dump(so,f)
with open("pickle_so","rb") as f:
lo_so = pickle.load(f)
print(lo_so.name)
执行结果:
李城
## 第二步
import pickle
class coso:
def __init__(self,name,age):
self.name = name
self.age = age
so = coso("江凡",22)
print(so.name)
# so.name = "李城"
# with open("pickle_so","wb") as f:
# pickle.dump(so,f)
with open("pickle_so","rb") as f:
lo_so = pickle.load(f)
print(lo_so.name)
执行结果:
江凡
李城
## 有没有看出什么,这样子就类似于存档之类的用法
# shelve模块
shelve模块只有二个命令,读取文件跟关闭文件
打开文件并添加数据
shelve模块默认会创建三个文件,文件名.bak、文件名.dat、文件名.dir,这三个文件
import shelve
so = shelve.open("shelve_so")
so["key"] = "value"
so.close()
查看数据
查看数据也是需要打开文件的
import shelve
so = shelve.open("shelve_so")
sh_so = so["key"]
so.close()
print(sh_so)
执行结果:
value
总结
shelve模块存储类似字典方式,一个Key值对应一个value值
查询的时候,跟查看字典一样,输入对应的key值就能查询到对应的value值
不常用,如需用,请自行查阅官网文档