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()与文件操作无关

举例说明:
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 抓老鼠 --> 反序列化之后得到的对象还是原来那个类型的对象
View Code

2. pickle模块中的dump()方法和load()方法
注意: dump()方法和load()方法多用于文件的写入\写出操作

举例说明:
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()       # 对象可以访问类中的方法
View Code

3. pickle模块还支持多个对象的写入\写出

举例说明:
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()方法
View Code

以上操作是有问题的,因为我们在实际情况中是不知道文件内容中有多少个对象的.因此,我们需要换一种操作方式.

举例说明1:
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()方法
View Code

举例说明2:
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
View Code

对pickle模块的总结:
(1)pickle只能在python中使用,它只支持python这门语言,跨平台性较差.
(2)pickle序列化支持在python中的几乎所有数据类型.
(3)pickle中的dumps方法序列化的结果一定是字节.
(4)在进行文件操作时,需要用rb和wb模式打开文件.
(5)可以dump多个对象到文件中,也可以从文件中load出来多个对象(load次数超过对象个数会报错,注意捕获异常).


三. shelve模块
shelve提供python的持久化操作,即把数据写到硬盘上.
shelve的操作方式与字典非常相似,可以把它看作是"文件的字典式操作".

1. 增加
import shelve
helf = shelve.open("shelve_test1")
# print(shelf["jay"])      # 报错,因为文件中还没有"jay"的信息

shelf["jay"] = "周杰伦"    # 执行新增
print(shelf["jay"])        # 打印结果: 周杰伦 --> 新增成功

shelf.close()
View Code

2. 修改
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()
# 打印结果: 周杰伦 --> 修改失败
View Code

解决方案如下:
shelf = shelve.open("shelve_test1", writeback=True)
shelf["jay"]["name"] = "王力宏"     # 再次尝试修改
shelf.close()
shelf = shelve.open("shelve_test1")
print(shelf["jay"]["name"])         # 查看我们修改的内容
shelf.close()
# 打印结果: 王力宏 --> 修改成功
View Code

writeback=True可以动态地把我们修改的信息写入到文件中,而且它还可以删除数据,就像字典一样


3. 删除
shelf = shelve.open("shelve_test1", writeback=True)
del shelf["jay"]
shelf.close()
shelf = shelve.open("shelve_test1")
print(shelf["jay"])     # 打印结果: 报错 --> 因为之前已经把"jay"的数据给删除了
shelf.close()
View Code

4. 查找
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()
View Code



四. 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"))

 


posted @ 2018-10-22 21:17  咕噜噜~  阅读(417)  评论(0编辑  收藏  举报