Python 常用模块(2) 序列化(pickle,shelve,json,configpaser)
主要内容:
一. 序列化概述
二. pickle模块
三. shelve模块
四. json模块(重点!)
五. configpaser模块
一. 序列化概述
1. 序列化: 将字典,列表等内容转换成一个字符串的过程就叫做序列化.
2. 为什么要把其他数据类型转换成字符串?
(1)能够在网络上传输的只能是bytes
(2)能够存储在文件里的只有bytes和str
3. 序列化的目的:
(1)以某种存储形式使自定义对象持久化
(2)将对象从一个地方传递到另一个地方
(3)使程序更具维护性
4. 反序列化:
序列化: str --> 数据结构
反序列化: str <-- 数据结构
5. 在Python中序列化的三种方案:
(1)pickle模块: 可以将Python中的任意数据类型转换成bytes并写入到文件中,同样也可以把文件中写好的bytes转换回Python的数据,这个过程被称为反序列化.
(2)shelve模块: 它是一种简单另类的序列化方案,有一点类似于今后会学习的redis.它可以作为一种小型的数据库来使用.
(3)json模块: 将python中常用的字典,列表转化成字符串.它是目前前后端数据交互使用频率最高的一种数据格式.
二. pickle模块
pickle是把python对象写入到文件中的一种解决方案.写入到文件中的是bytes,它不是给人看的,只有机器可以识别.
pickle可以把python中任意的数据类型序列化.
1. pickle模块中的dumps()方法和loads()方法
注意: dumps()和loads()与文件操作无关
举例说明:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import pickle # 引入模块 class Cat: # 创建类 def __init__(self, name, age): self.name = name self.age = age def catch_mouse(self): print(self.name, "抓老鼠") cat = Cat("jerry", 18) # 创建一个对象 byte = pickle.dumps(cat) # dumps()方法 --> 序列化一个对象 print(byte) # 打印结果是一堆很长的二进制字符串 new_cat = pickle.loads(byte) # 把二进制字符串反序列化为原来的对象 new_cat.catch_mouse() # 执行结果: jerry 抓老鼠 --> 反序列化之后得到的对象还是原来那个类型的对象
2. pickle模块中的dump()方法和load()方法
注意: dump()方法和load()方法多用于文件的写入\写出操作
举例说明:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import pickle class Cat: def __init__(self, name, age): self.name = name self.age = age def catch_mouse(self): print(self.name, "抓老鼠") cat = Cat("jerry", 18) # 创建一个对象 with open("cat", mode="wb") as f: pickle.dump(cat, f) # 把对象cat以二进制字符串的形式写进文件中 with open("cat", mode="rb") as f: new_cat = pickle.load(f) # 从文件中读取信息,并把信息反序列化为对象 new_cat.catch_mouse() # 对象可以访问类中的方法
3. pickle模块还支持多个对象的写入\写出
举例说明:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import pickle class Cat: def __init__(self, name, age): self.name = name self.age = age def catch_mouse(self): print(self.name, "抓老鼠") lst = [Cat("jerry", 19), Cat("tommy", 20), Cat("kendy", 21)] with open("cat", mode="wb") as f: for el in lst: pickle.dump(el, f) # 把对象序列化并写入文件 with open("cat", mode="rb") as f: for i in range(len(lst)): # 我们可能事先不知道列表中到底有多少个对象 new_cat = pickle.load(f) # 把文件中的二进制反序列化为对象 new_cat.catch_mouse() # 对象访问catch_mouse()方法
以上操作是有问题的,因为我们在实际情况中是不知道文件内容中有多少个对象的.因此,我们需要换一种操作方式.
举例说明1:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import pickle class Cat: def __init__(self, name, age): self.name = name self.age = age def catch_mouse(self): print(self.name, "抓老鼠") lst = [Cat("jerry", 19), Cat("tommy", 20), Cat("cendy", 21)] with open("cat", mode="wb") as f: pickle.dump(lst, f) # 直接把整个列表序列化并写进文件中 with open("cat", mode="rb") as f: new_lst = pickle.load(f) # 读取文件中的信息,并将其反序列化,拿到一个列表 for el in new_lst: # 遍历整个列表 el.catch_mouse() # 每一个元素都可以访问catch_mouse()方法
举例说明2:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import pickle dic1 = {(1, 2, 3):{'a', 'b'}, 1:'abc'} dic2 = {(1, 2, 3):{'a', 'b'}, 2:'abc'} dic3 = {(1, 2, 3):{'a', 'b'}, 3:'abc'} dic4 = {(1, 2, 3):{'a', 'b'}, 4:'abc'} with open ("pickle_file", "wb") as f: pickle.dump(dic1, f) pickle.dump(dic2, f) pickle.dump(dic3, f) pickle.dump(dic4, f) # 第一种读取方式: with open("pickle_file", "rb") as f: ret = pickle.load(f) print(ret, type(ret)) ret = pickle.load(f) print(ret, type(ret)) ret = pickle.load(f) print(ret, type(ret)) ret = pickle.load(f) print(ret, type(ret)) # ret = pickle.load(f) # EOFError: Ran out of input # print(ret, type(ret)) # 如果文件中的对象已经全部被load(拿出来反序列化)了,此时再次load就会报错 # 以上代码执行结果: # {(1, 2, 3): {'b', 'a'}, 1: 'abc'} <class 'dict'> # {(1, 2, 3): {'b', 'a'}, 2: 'abc'} <class 'dict'> # {(1, 2, 3): {'b', 'a'}, 3: 'abc'} <class 'dict'> # {(1, 2, 3): {'b', 'a'}, 4: 'abc'} <class 'dict'> # 改进后的第二种读取方式: with open("pickle_file", "rb") as f: while True: try: ret = pickle.load(f) print(ret, type(ret)) except EOFError: # 异常被捕获了 break
对pickle模块的总结:
(1)pickle只能在python中使用,它只支持python这门语言,跨平台性较差.
(2)pickle序列化支持在python中的几乎所有数据类型.
(3)pickle中的dumps方法序列化的结果一定是字节.
(4)在进行文件操作时,需要用rb和wb模式打开文件.
(5)可以dump多个对象到文件中,也可以从文件中load出来多个对象(load次数超过对象个数会报错,注意捕获异常).
三. shelve模块
shelve提供python的持久化操作,即把数据写到硬盘上.
shelve的操作方式与字典非常相似,可以把它看作是"文件的字典式操作".
1. 增加
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import shelve helf = shelve.open("shelve_test1") # print(shelf["jay"]) # 报错,因为文件中还没有"jay"的信息 shelf["jay"] = "周杰伦" # 执行新增 print(shelf["jay"]) # 打印结果: 周杰伦 --> 新增成功 shelf.close()
2. 修改
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import shelve shelf = shelve.open("shelve_test1") print(shelf["jay"]) # 像操作字典一样直接进行修改: shelf["jay"] = {"name": "周杰伦", "age": 18, "hobby": "唱歌"} print(shelf["jay"]) shelf.close() # 执行结果: # 周杰伦 # {'name': '周杰伦', 'age': 18, 'hobby': '唱歌'} 尝试修改{'name': '周杰伦', 'age': 18, 'hobby': '唱歌'}这个字典中的内容: shelf = shelve.open("shelve_test1") shelf["jay"]["name"] = "王力宏" # 尝试修改 shelf.close() shelf = shelve.open("shelve_test1") print(shelf["jay"]["name"]) # 查看我们修改的内容 shelf.close() # 打印结果: 周杰伦 --> 修改失败
解决方案如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
shelf = shelve.open("shelve_test1", writeback=True) shelf["jay"]["name"] = "王力宏" # 再次尝试修改 shelf.close() shelf = shelve.open("shelve_test1") print(shelf["jay"]["name"]) # 查看我们修改的内容 shelf.close() # 打印结果: 王力宏 --> 修改成功
writeback=True可以动态地把我们修改的信息写入到文件中,而且它还可以删除数据,就像字典一样
3. 删除
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
shelf = shelve.open("shelve_test1", writeback=True) del shelf["jay"] shelf.close() shelf = shelve.open("shelve_test1") print(shelf["jay"]) # 打印结果: 报错 --> 因为之前已经把"jay"的数据给删除了 shelf.close()
4. 查找
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
shelf = shelve.open("shelve_test1", writeback=True) shelf["乐坛半壁江山"] = "汪峰" shelf["华仔"] = "刘德华" shelf["星爷"] = "周星驰" shelf.close() # 遍历文件拿到所有key shelf = shelve.open("shelve_test1") for k in shelf: print(k) # 拿到所有key for k in shelf.keys(): print(k) # 拿到所有key for v in shelf.values(): print(v) # 拿到所有value for k, v in shelf.items(): print(k, v) # 拿到所有key和value shelf.close()
四. json模块
json模块提供了四个功能: 序列化(dumps和dump), 反序列化(loads和load)
如下实例:
import json dic = {'key': 'value', 'key2': 'value2'} ret = json.dumps(dic) # 序列化 print(dic, type(dic)) # {'key': 'value', 'key2': 'value2'} <class 'dict'> print(ret, type(ret)) # {"key": "value", "key2": "value2"} <class 'str'> res = json.loads(ret) # 反序列化 print(res, type(res)) # {'key': 'value', 'key2': 'value2'} <class 'dict'>
# json能够序列化的数据有什么特点?观察下面几个示例,分析结果. # 特点1: 字典的key是整型,经过序列化和反序列化之后变成了字符串类型 # 特点2: 字典的value是元组, 经过序列化和反序列化之后变成了列表类型 dic = {1:[1,2,3], 2:(4,5,'aa')} ret = json.dumps(dic) # 序列化 print(dic, type(dic)) # {1: [1, 2, 3], 2: (4, 5, 'aa')} <class 'dict'> print(ret, type(ret)) # {"1": [1, 2, 3], "2": [4, 5, "aa"]} <class 'str'> res = json.loads(ret) # 反序列化 print(res, type(res)) # {'1': [1, 2, 3], '2': [4, 5, 'aa']} <class 'dict'> # 特点3: set集合类型不能被json序列化 # 特点4: 字典的键必须是字符串才能被json序列化 s = {1, 2, "aaa"} json.dumps(s) # 报错: TypeError: Object of type 'set' is not JSON serializable json.dumps({(1,2,3):123}) # 报错: TypeError: keys must be a string
总结: json在所有的语言之间都通用:即在python中json序列化后的数据,把它拿到java中也可以反序列化,反之亦然.
可以认为,json序列化后的数据,在其他语言中也能够反序列化回来,所有语言都可以识别"json序列化后的数据".
由此也导致json能够处理的数据非常有限,只有 字符串,列表,字典,数字 这几种类型,而且字典中的key只能是字符串.
# 向文件中写入字典 import json dic = {'key' : 'value','key2' : 'value2'} ret = json.dumps(dic) # 序列化(这里是将序列化结果全部写入内存,下面的代码再从内存中读取全部结果并写入文件) with open("json_file", "a") as f: f.write(ret) # 从内存中读取数据,并写入文件中 # 从文件中读取字典 with open ("json_file", "r") as f: str_dic = f.read() # 读取全部文件内容并将其写入内存 dic = json.loads(str_dic) # 将内存中的字符串反序列化 print(dic) # 打印结果: {'key': 'value', 'key2': 'value2'} # dump和load是直接操作文件的,如下示例: dic = {'key1' : 'value1','key2' : 'value2'} with open('json_file', 'a') as f: json.dump(dic, f) # 把dic序列化并写入文件json_file中 with open('json_file', 'r') as f: dic = json.load(f) # 把文件内容反序列化为字典 print(dic) # {'key1': 'value1', 'key2': 'value2'}
总结: 如果我们是进行文件相关的操作(读/写),那么可以用dump和load.如果是处理网络上传输的数据是,由于此时数据都是在内存中,这就要用到dumps和loads了.
# 特点5: 不支持连续的存取 dic = {'key1':'value1', 'key2':'value2'} with open("json_file", "a") as f: json.dump(dic, f) json.dump(dic, f) json.dump(dic, f) with open("json_file", "r") as f: dic = json.load(f) print(dic.keys())
总结: 上面程序中虽然成功通过dump多次向文件中存入3个字典,但是load会报错. 也就是说load只能读取"存一个字典"的文件,嵌套字典也可以,但最外层只能是一个.
# 解决办法 dic = {'key1':'value1', 'key2':'value2'} with open("json_file", "a") as f: str_dic = json.dumps(dic) f.write(str_dic + "\n") str_dic = json.dumps(dic) f.write(str_dic + "\n") str_dic = json.dumps(dic) f.write(str_dic + "\n") with open("json_file", "r") as f: for line in f: dic = json.loads(line.strip()) print(dic) # 执行结果: # {'key1': 'value1', 'key2': 'value2'} # {'key1': 'value1', 'key2': 'value2'} # {'key1': 'value1', 'key2': 'value2'}
综上所述:
json的dumps和loads -- 在内存中做数据转换:
dumps(序列化) --> 数据类型 转成 字符串
loads(反序列化) --> 字符串 转成 数据类型
json的dump和load -- 直接将数据类型写入文件,直接从文件中读出数据类型:
dump(序列化) --> 把数据类型序列化并写入文件
load(反序列化) --> 从文件中读出内容并将其反序列化为数据类型
json是所有语言都通用的一种序列化格式,只支持列表,字典,字符串,数字,并且字典的key必须是字符串.
# ensure_ascii 关键字参数 dic = {"key":"你好} print(json.dumps(dic)) # {"key": "\u4f60\u597d"} print(json.dumps(dic, ensure_ascii=False)) # {"key": "你好"}
# json 的格式化输出 data = {"username":["赵日天", "二愣子"], "gender":"male", "age":16} json_dic = json.dumps(data, sort_keys=True, indent=4, separators=(',',':'), ensure_ascii=False) print(json_dic) # 执行结果: # { # "age":16, # "gender":"male", # "username":[ # "赵日天", # "二愣子" # ] # }
五. configparser模块
该模块适⽤于配置⽂件的格式与windows下的ini⽂件类似,可以包含⼀个或多个节(section),每个节
可以有多个参数(键=值).
import configparser config = configparser.ConfigParser() config['DEFAULT'] = { "sleep": 1000, "session_time_out": 30, "user_alive": 999999 } config['TEST-DB'] = { "db_ip": "192.168.17.189", "port": "3306", "u_name": "root", "u_pwd": "123456" } config['168-DB'] = { "db_ip": "152.163.18.168", "port": "3306", "u_name": "root", "u_pwd": "123456" } config['173-DB'] = { "db_ip": "152.163.18.173", "port": "3306", "u_name": "root", "u_pwd": "123456" } f = open("db.ini", mode="w") config.write(f) # 写⼊⽂件 f.flush() f.close()
# 读取⽂件信息: config = configparser.ConfigParser() config.read("db.ini") # 读取⽂件 print(config.sections()) # 获取到section章节, DEFAULT是给每个章节都配备的信息 print(config.get("DEFAULT", "SESSION-TIME-OUT")) # 从xxx章节中读取到xxx信息 # 也可以像字典⼀样操作 print(config["TEST-DB"]['DB_IP']) print(config["173-DB"]["db_ip"]) for k in config['168-DB']: print(k) for k, v in config["168-DB"].items(): print(k, v) print(config.options('168-DB')) # 同for循环,找到'168-DB'下所有键 print(config.items('168-DB')) # 找到'168-DB'下所有键值对 print(config.get('168-DB','db_ip')) # 152.163.18.168 get⽅法Section下的key对应的value # 增删改操作: # 先读取,然后修改,最后写回⽂件 config = configparser.ConfigParser() config.read("db.ini") # 读取⽂件 # 添加⼀个章节 config.add_section("189-DB") config["189-DB"] = { "db_ip": "167.76.22.189", "port": "3306", "u_name": "root", "u_pwd": "123456" } # 修改信息 config.set("168-DB", "db_ip", "10.10.10.168") # 删除章节 config.remove_section("173-DB") # 删除元素信息 config.remove_option("168-DB", "u_name") # 写回⽂件 config.write(open("db.ini", mode="w"))