共计 3647 个字符,预计需要花费 10 分钟才能阅读完成。
引入
1.eval() 函数
- 在 内置函数 这一张中我们介绍过了 eval() 函数, 他可以将字符串转成Python 对象
- 不过局限性比较大, 只能运用于普通的数据类型, 遇到特殊的数据类型就不管用 了
- eval() 的处理重点 : 执行一个字符串表达式, 并返回表达式的值
2.eval 函数存取数据示例
🍔通过文件将字典转成字符串存入(序列化)dic = {'name':'egon','age':18}
with open('db.txt','w',encoding='utf-8')as f :
f.write(str(dic))
🍔通过 "eval" 函数将拿出来的字符串转成数据结构(反序列化)with open('db.txt','r',encoding='utf-8')as f:
data = f.read()
dic = eval(data)
print(dic) # {'name': 'egon', 'age': 18}
print(type(dic)) # <class 'dict'>
3.eval 的局限性
x="[null,true,false,1]"
print(eval(x)) # 报错,无法解析 null 类型
一. 什么是序列化和反序列化
1. 序列化
-
把某个语言的变量转成 json 格式字符串
-
内存中的数据结构 -----> 转成一种中间格式 (字符串)-----> 存到文件中
2. 反序列化
-
把 json 格式字符串转成某个语言的变量
-
文件 -----> 读取中间格式 (字符串)-----> 转成内存中数据结构
3.json 的标准格式字符串
- {"name" : "shawn", "age" : 18, "local" : true, "xx" : null}
- json 格式字符串符号 必须是双引号
二. 为什么要序列化
序列化两种用途:
1. 持久保持状态
- 一个软件或程序的运行就是在 处理一系列状态 的变化, 编程语言中, "状态" 会以各种各样有结构的数据类型 (或变量) 保存在内存中
- 而内存是无法永久保存数据的, 当断电或者是重启程序, 内存中那些有结构的数据都会被清空
- 那么序列化就是在你机器在断电或重启之前将当前内存中的数据都保存到文件中去, 下次执行程序可以直接载入之前的数据, 然后继续执行 (就相当于单机游戏存档)
存取数据的格式标准, 一个程序写入, 另一个程序读取 (这两个程序可以是使用不同的语言写的)
2. 跨平台数据交互
- 序列化之后, 不仅可以将序列化后的内容 存入磁盘 , 也可以 通过网络传输 到别的机器上
- 如果发送和接收方都约定好使用同一种序列化的格式, 那么便屏蔽了平台和语言所带来的的差异性, 实现了跨平台数据交互
- 反过来, 把数据 (变量) 内容从序列化的对象 重新读到内存中 , 这就称之为 反序列化
例如 : 后端给前端的数据就是 "json" 格式字符串
三. json 能序列化的类型
-
json 模块可以序列化:字典,列表,布尔
-
json 数据类型和 python 数据类型对应关系表
Json 类型 | Python 类型 |
---|---|
{} | dict |
[] | list |
"string" | str |
1.73 | int 或 float |
true/false | True/False |
null | None |
- 序列化三种类型示例 (简单使用方法)
dic = {'movieIds': [111, 222, 333, 444],\
'stids': [{'movieId': 11, 'stid': 1}, \
{'movieId': 22, 'stid': 2}, \
{'movieId': 33, 'stid': 3}]}
res = json.dumps(dic)
print(res)
''' 可以发现对应格式已经发生了转变
{"movieIds": [111, 222, 333, 444],\
"stids": [{"movieId": 11, "stid": 1},{"movieId": 22, "stid": 2}, {"movieId": 33, "stid": 3}]}
'''
四.json 序列化的使用
1. 简单用法
import json
dic = {"name":"song"}
print(json.dumps(dic)) # {"name": "song"}
s = '{"name": "song"}'
print(json.loads(s)) # {'name': 'song'}
2. 写入文件序列化与反序列化 (复杂)
- 序列化 :
.dumps()
import json
dic = {'name':'song','age':21}
dic = json.dumps(dic)
print(dic,type(dic)) # {"name": "song", "age": 21} <class 'str'>
with open('db.json','a',encoding='utf-8')as f:
f.write(dic)
- 反序列化 :
.loads()
with open('db.json', 'rt', encoding='utf-8')as f:
data = f.read()
dic = json.loads(data)
print(dic, type(dic)) # {'name': 'song', 'age': 21} <class 'dict'>
3. 简单序列化与反序列化 (提供直接写入文件功能)
- 序列化 :
.dump()
- 直接以字符串写进文件
import json
dic = {'name':'song','age':21}
with open('db1.json','wt',encoding='utf-8')as f:
json.dump(dic,f)
- 反序列化 :
.load()
- 直接拿出来转成你的数据类型
with open('db1.json','rt',encoding='utf-8')as f:
dic = json.load(f)
print(dic,dic['age']) # {'name': 'song', 'age': 21} 21
五. 中文序列化问题
- 演示
import json
dic = {"name": " 派大星 ", "age": 22}
res = json.dumps(dic)
print(res) # {"name": "\u6d3e\u5927\u661f", "age": 22}
- 加入
ensure_ascii=False
功能
dic = {"name": " 派大星 ", "age": 22}
res = json.dumps(dic,ensure_ascii=False)
print(res) # {"name": " 派大星 ", "age": 22}
- 问题总结
并没有使用上的变化, 存的是什么, 取出来的还是什么, 只不过人家内部帮你转成 "uncode" 格式保存
ps : java 中,出于性能的考虑,有很多包来完成序列化和反序列化:谷歌的 gson 阿里开源 fastjson 等等
六. 猴子补丁
1. 什么是猴子补丁
-
在 运行时动态的修改 模块, 类, 函数, 通常是添加功能或修正缺陷
-
猴子补丁在代码运行内存中发挥作用, 不会修改源代码, 因此 只在运行时有效
猴子补丁(Monkey Patch)
这个词原来为 Guerrilla Patch,杂牌军、游击队,说明这部分不是原装的,在英文里 guerilla 发音和 gorllia(猩猩)相似,再后来就写了 monkey(猴子)
还有一种解释是说由于这种方式将原来的代码弄乱了(messing with it),在英文里叫 monkeying about(顽皮的),所以叫做 Monkey Patch
2. 猴子补丁的功能演示
一切皆对象, 拥有模块, 类, 函数运行时替换的功能
一个函数对象赋值给另一个函数对象
将函数原本执行的功能替换掉了
class aaa:
def bbb(self):
print("i am bbb")
def ccc(self):
print("i am ccc")
def ddd():
print("i am ddd")
AAA = aaa()
AAA.bbb = AAA.ccc
AAA.bbb() # i am ccc
AAA.ccc = ddd
AAA.ccc() # i am ddd
3. 猴子补丁的应用场景
如果我们基于 json 模块写了大量的代码, 然后发现 ujson 比它快 10 倍, 并且用法一样
我们肯定不会想到把所有的 json.dumps 或 json.loads 全换成 ujson.dumps 或 ujson.loads
我们可能会想到 模块取别名 import json as ujson, 但这样每个模块都需要重新导入一下
以上方法的维护成本都非常高, 此时我们就可以使用到猴子补丁了, 只需要在入口文件加上就行
import json
import ujson
def monkey_patch_json():
json.__name__ = 'ujson'
json.dumps = ujson.dumps
json.loads = ujson.loads
monket_patch_json()
4. 猴子补丁优缺点总结
之所以在入口处加, 是因为模块在导入一次之后, 后续的导入变直接引用第一次导入的结果
其实这种场景也比较多, 比如我们引用团队通用库里的一个模块, 又想丰富模块的功能, 除了继承之外也可以考虑用 Monkey Patch
优点 : 采用猴子补丁后, 如果发现 ujson 不符合预期, 那也可以快速撤回补丁
缺点 : 因为 Monkey Patch 破坏了封装, 带来了搞乱源代码的风险