07-09 json&pickle模块

一. 由eval引出json和pickle

# 引入: eval内置方法可以将一个字符串转成python对象,不过,eval方法是有局限性的, 对于普通的数据类型eval可以使用, 但遇到特殊类型的时候,eval就不管用了, eval的重心还是通常用来执行一个字符串表达式,并返回表达式的值
import json

res = eval('1 + 2')
print(res)  # eval会执行一个字符串表达式, 返回结果3

# eval('[null, true, false, 1]')  # 抛出异常(NameError: name 'null' is not defined), eval无法解析null等类型, 而这个时候就用到了json

print(json.loads('[null, true, false, 1]'))  # [None, True, False, 1]

二. 什么是序列化? 反序列化?

"""
序列化: 指的是把内存的数据类型转换成一个特定的格式的内容。且该格式的内容可用于存储或者传输给其他平台使用。
反序列化: 指的是在硬盘中的特定的格式,再转化成内存中的数据类型。
"""
# 内存中的数据类型 ---> 序列化 ---> 特定的格式。(json格式或者pickle)
# 内存中的数据类型 <--- 反序列化 <--- 特定的格式。(json格式或者pickle)

三. 为何要序列化?

"""
# 序列化得到的结果转化为特定的格式的内容有两种用途	
	1. 持久保存状态: 需知一个软件/程序的执行就在处理一系列状态的变化,在编程语言中,'状态'会以各种各样有结构的数据类型(也可简单的理解为变量)的形式被保存在内存中。内存是无法永久保存数据的,当程序运行了一段时间,我们断电或者重启程序,内存中关于这个程序的之前一段时间的数据都被清空了。在断电或重启程序之前将程序当前内存中所有的数据都保存下来(保存到文件中),以便于下次程序执行能够从文件中载入之前的数据,然后继续执行,这就是序列化。例: 用于游戏存档
	2. 跨平台数据交互: 序列化之后,不仅可以把序列化后的内容写入磁盘,还可以通过网络传输到别的机器上,如果收发的双方约定好实用一种序列化的格式,那么便打破了平台/语言差异化带来的限制,实现了跨平台数据交互
	
# 强调
	针对用途1的特定一格式:可是一种专用的格式(pickle只有python可以识别)
	针对用途2的特定一格式:应该是一种通用、能够被所有语言识别的格式(json: 我们要注意这里指的是通用的数据类型。集合类型并不能被识别。集合类型属于Python独有的一种类型, 不是一种通用的类型.)
"""

四. 如何序列化与反序列化之json

1.json和python内置的数据类型的对应关系图

2.json序列化与反序列化基本用法

import json

# 1. 序列化: 注意!序列化以后。返回的类型是字符串类型。
res = json.dumps([1, 1.1, True, False, None, {'name': 'egon'}])
print(res, type(res))  # [1, 1.1, true, false, null, {"name": "egon"}] <class 'str'>

# 2. 反序列化: 序列化以后。拿回到了原来的类型。
res = json.loads(res)
print(res, type(res))  # [1, 1.1, True, False, None, {'name': 'egon'}] <class 'list'>

3.json序列化的结果写入文件与读取json字符串格式进行反序列化操作

import json

# 一. 序列化的结果写入文件
# 1. 方法一: json.dumps
info = [1, 1.1, True, False, None, {'name': 'egon'}]
res = json.dumps(info) # 提示: 序列化以后返回的是字符串类型,所以可以写入写入文件。
with open('a.json', 'wt', encoding='utf-8') as f:
    f.write(res)

# 2. 方法二: json.dump, 写入写入文件推荐使用这种方式
info = [1, 1.1, True, False, None, {'name': 'egon'}]
with open('a.json', 'wt', encoding='utf-8') as f:
    json.dump(info, f)
    

# 二. 从文件读取json格式的字符串进行反序列化操作  
# 1. 方式一: json.loads
with open('a.json', 'rt', encoding='utf-8') as f:
    res = json.loads(f.read())
    print(res, type(res))  # [1, 1.1, True, False, None, {'name': 'egon'}] <class 'list'>

# 2. 方式二: json.load, 文件读json格式的字符串,推荐使用这种方式。
with open('a.json', 'rt', encoding='utf-8') as f:
    print(json.load(f))

4.使用json的4点注意须知

import json

# 须知一: json兼容的是所有语言通用的数据类型。不能识别某一语言的独有类型。而Python中的独有类型就含有集合, 所以json不能识别。
json.dumps({1, 2, 3})  # TypeError: Object of type set is not JSON serializable

# 须知二: json格式中的字符串必须使用双引号。json 不认单引号. 
json.loads("['logging_test', 'config_test']")  # json.decoder.JSONDecodeError: Expecting value: line 1 column 2 (char 1)

# 须知三. 无论数据是怎样创建的,只要满足json格式,就可以json.loads出来,不一定非要dumps的数据才能loads(上面如图所示。展示json格式是怎么样表达的)

# 须知四. json针对中文时的序列化与反序列化(针对中文的序列化会把它转成一种特定的json编码格式, 并不能与英文字符串一样能直观的看到。)
res = json.dumps('我是中文')
print(res, type(res))  # "\u6211\u662f\u4e2d\u6587" <class 'str'>

res = json.loads(res)
print(res, type(res))  # 我是中文 <class 'str'>

5.使用json序列化bytes类型须知

# 了解: python2当中str就是bytes类型. 在python解释器2.7与3.6之后都可以json.loads(bytes类型), python3中字符串其实等同于Unicode编码格式。但是在除了python3.5, json内部对这种的表达形式做了优化, 让在python3中也可以使用,但唯独3.5不可以
>>> import json
>>> json.loads(b'{"a":111}')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/linhaifeng/anaconda3/lib/python3.5/json/__init__.py", line 312, in loads
    s.__class__.__name__))
TypeError: the JSON object must be str, not 'bytes'  

6.猴子补丁

"""
# 什么是猴子补丁?
	猴子补丁的核心就是用自己的代码替换所用模块的源代码
# 猴子补丁的功能(一切皆对象)
	拥有在模块运行时替换的功能
	
# 思路: 这里利用的就是模块的导入,在第一次导入的时候,后面的导入的代码并不会真正意义上的去执行。只会申请第一次导入内存时对应模块文件的名称空间。因为这一点我们就可以针对模块导入时点下面执行的功能进行替换。一个程序的入口。是在脚本文件中我们通过脚本文件也就是当前可执行文件中。我们使用如下的这种替换功能。	
"""
# monkey patch的应用场景:
# 如果我们的程序中已经基于json模块编写了大量代码了,发现有一个模块ujson比它性能更高,但用法一样,我们肯定不会想所有的代码都换成ujson.dumps或者ujson.loads,那我们可能会想到这么做
import ujson as json # 但是这么做的需要每个文件都重新导入一下,维护成本依然很高,此时我们就可以用到猴子补丁了


# 猴子补丁的运用代码示例: 注意! 只需要在入口加上, 也就是执行脚本文件/启动脚本文件.
import json
import ujson  
def monkey_patch_json():
    json.__name__ = ujson.__name__
    json.dump = ujson.dump
    json.load = json.load

monkey_patch_json() # 之所以在入口处加,是因为模块在导入一次后,后续的导入便直接引用第一次的成果   
"""
结言:
利用猴子补丁的这种场景其实也比较多, 比如我们引用团队通用库里的一个模块, 又想丰富模块的功能, 除了继承之外也可以考虑用Monkey
Patch.采用猴子补丁之后,如果发现ujson不符合预期,那也可以快速撤掉补丁。个人感觉Monkey
Patch带了便利的同时也有搞乱源代码的风险!
"""

五. 如何序列化与反序列化之pickle

1.pickle的使用问题与局限性

"""
# pickle使用局限性
	picklee的问题和所有其他编程语言特有的序列化问题一样,就是它只能用于Python,并且可能不同版本的Python彼此都不兼容,因此,只能用Pickle保存那些不重要的数据,不能成功地反序列化也没关系。
"""

2.pickle序列化与反序列化基本用法与json的区别

import pickle

# 序列化
"""
pickle序列化之后返回的结果是一个bytes类型, 而Json返回的是一个字符串类型。
Json中没有对应集合数据类型。而pickle是Python中独有的序列化与反序列化转换的格式。所以它可以转化python中的任意的数据类型, 也包括集合数据类型.
"""
res = pickle.dumps({1, 2, 3})
print(res)  # b'\x80\x04\x95\x0b\x00\x00\x00\x00\x00\x00\x00\x8f\x94(K\x01K\x02K\x03\x90.'

# 反序列化
print(pickle.loads(res))  # {1, 2, 3}

3.pickle序列化的结果写入文件与读取pickle格式的bytes类型进行反序列化操作

import pickle

"""
提示: pickle序列化以后。拿到的返回值是一个buys类型。所以针对文件的写操作, 直接使用b模式(二进制模式/bytes模式)
注意: 直接pickle序列化以后存入文件的内容并不能看到。只能使用picco反序列化以后。才能进行打印显示。
"""
info = [1, 1.1, True, False, None, {'name': 'egon'}]
with open('a.pickle', 'wb') as f:
    pickle.dump(info, f)

with open('a.pickle', 'rb') as f:
    print(pickle.load(f))  # [1, 1.1, True, False, None, {'name': 'egon'}]

4.python2与python3的pickle兼容性问题

# coding:utf-8
import pickle

with open('a.pkl',mode='wb') as f:
    # 一:在python3中执行的序列化操作如何兼容python2
    # python2不支持protocol>2,默认python3中protocol=4
    # 所以在python3中dump操作应该指定protocol=2, 为了方便以后兼容python2的协议
    pickle.dump('你好啊',f,protocol=2)

with open('a.pkl', mode='rb') as f:
    # 二:python2中反序列化才能正常使用
    res=pickle.load(f)
    print(res)
posted @ 2020-03-31 23:49  给你加马桶唱疏通  阅读(168)  评论(0编辑  收藏  举报