序列化模块
将原本的字典、列表转化成字符串的过程就叫序列化
序列化的目的
- 以某种存储形式使自定义对象持久化
- 将对象从一个地方传递到另一个地方
- 使程序更具有维护性
json模块
Json模块提供了四个功能:dumps、dump、loads、load
import jsondict1 = {"k1": "v1", "k2": "v2", "k3": "v3"}list1 = [1, ['a', 'b', 'c'], {"k2": "v2", "k3": "v3"}, (1, 2), True]str_dict1 = json.dumps(dict1)print("序列化字符串:", repr(str_dict1))dict2 = json.loads(str_dict1)print("反序列化字典:", repr(dict2))str_list1 = json.dumps(list1)print("序列化字符串:", repr(str_list1))list2 = json.loads(str_list1)print("反序列化列表:", repr(list2))
- dumps
| 参数 | 功能 |
| —- | —- |
| Skipkeys |
1. 默认值为False
1. 如果dict内的keys内数据不是python的基本类型, Skipkeys值为False时,会报TypeError的错误; Skipkeys值为True时,则会跳过这类key
1. Skipkeys值为True时,所有的非ascii码字符会显示为\uXXXX序列,我们可以把ensure_ascii设置为False即可正常显示中文
| | indent | 是一个非负的整型,如果值为0,则顶格分行显示;如果值为空,则会在这一行紧凑显示;如果值为其他,则会前面空白indent的值分行显示。
这样打印出来的json数据也叫pretty-printed json | | ensure_ascii | 当值为True时,所有的非ascii码显示为\uXXXX序列,当值为False时,中文可以正常显示 | | separators | 分隔符,其实是一个(item_separators, dict_separators)的元组,默认是(‘,’, ‘:’),表示字典内keys之间用’,’隔开,而key和value之间由’:’隔开 | | sort_keys | 是否将数据根据keys的值进行排序 |
import jsondata = {'name': '张三', 'sex': 'male', 'age': 18}json_dic = json.dumps(data, sort_keys=True, indent=2, separators=(',', ':'), ensure_ascii=False)print(json_dic)# 运行结果{"age":18,"name":"张三","sex":"male"}
json.dump和json.load不常用,主要是针对文件操作进行序列化和反序列化
import jsonv = {'k1': 'yh', 'k2': '小马过河'}f = open('xiaoma.txt', mode='w+', encoding='utf-8') # 文件不存在就会生成json.dump(v, f)f.close()# xiaoma.txt{"k1": "yh", "k2": "\u5c0f\u9a6c\u8fc7\u6cb3"}
pickle模块
| pickle | json |
|---|---|
| 只能在python之间交互 | 可以在python与其它语言之间交互 |
| 可以序列化python所有数据类型 | 只能序列化python常规数据类型,如列表 |
| pickle的序列化后的对象不可读 | json序列化后的对象是可读的 |
pickle模块提供了四个功能:dumps、dump(序列化,存)、loads、load(反序列化,读)
简单来说,json模块和pickle模块都有 dumps、dump、loads、load四种方法,而且用法一样。不同的是json模块序列化出来的是通用格式,其它编程语言都认识,就是普通的字符串,而pickle模块序列化出来的只有python可以认识,其他编程语言不认识的,表现为乱码。
不过pickle可以序列化函数,但是其他文件想用该函数,在该文件中需要有该文件的定义
import timestruct_time = time.localtime(1000000000)print(struct_time)f = open('pickle_file','wb')pickle.dump(struct_time,f)f.close()f = open('pickle_file','rb')struct_time2 = pickle.load(f)print(struct_time2.tm_year)
shelve模块
shelve也是python提供的序列化工具,shelve只提供给我们一个open方法,是用key来访问的,使用起来和字典有些类似。
用途:shelve是对象持久化的保存方法,将对象保存到文件里,默认的数据存储文件是二进制的。
shelve使用方法:
- 创建一个shelf对象,直接使用open函数即可
- 如果想要再次访问这个shelf,只需要再次shelve.open()就可以了,然后我们可以像使用字典一样来使用这个shelf ```python import shelve f = shelve.open(‘shelve_file’) f[‘key’] = {‘int’:10,’str’:’hello’,’float’:0.123} f.close()
f1 = shelve.open(‘shelve_file’) ret = f1[‘key’] f1.close() print(ret)
这个模块有一个限制,就是不允许多个应用在同一时刻向同一个文件里进行写操作,所以如果我们知道应用只是进行读操作,可以把shelve以只读的方式打开:`f1 = shelve.open('shelve_file',flag='r')`shelve在默认情况下不会记录待持久化对象的任何修改,所以如果我们要保存对象的修改,需要在shelve.open时改默认参数writeback。```pythonimport shelvef1 = shelve.open('shelve_file')print(f1['key'])f1['key']['k1'] = 'v1'f1.close()f2 = shelve.open('shelve_file',writeback=True)print(f2['key'])f2['key']['k1'] = 'hello'f2.close()
使用shelve实现一个简单的数据库。
# 使用shelve实现一个简单数据库import shelve# 增加一个人的数据def store_person(db):pid = input("请输入一个id:")person = dict()person['name'] = input("请输入姓名:")person['age'] = input("请输入年龄:")person['phone'] = input("请输入电话号码:")# 将这条字典数据存入数据库文件中db[pid] = personprint("存储信息:pid是%s,存储的内容是%s" % (pid, person))pass# 查找一个人的数据def search_person(db):pid = input("请输入要查询的id:")msg = input("请输入要查询的信息:(name、age、phone)")if pid in db.keys():value = db[pid][msg]print("pid %s 的 %s 是 %s" % (pid, msg, value))else:print("没找到输入的pid")pass# 修改一个人的数据def update_person(db):pid = input("请输入要修改的id:")msg = input("请输入要修改的信息:(name、age、phone)")newvalue = input("请输入新的内容:")if pid in db.keys():value = db[pid]value[msg] = newvalueprint("pid %s 的 %s 修改为了%s" % (pid, msg, newvalue))else:print("没找到输入的pid")pass# 删除一个人的数据def del_person(db):pid = input("请输入要删除的id:")if pid in db.keys():del db[pid]print("pid %s的数据已被删除" % pid)else:print("没找到输入的pid")passdef print_all(db):print("打印所有数据:")for key, value in db.items():print(key, value)passdef enter_cmd():cmd = input("请输入命令:")cmd = cmd.strip().lower()return cmddef print_help():print("命令:")print("add 增加一个人的数据")print("del 删除一个人的数据")print("update 修改一个人的数据")print("search 查询一个人的数据")print("print_all 打印所有数据")print("quit 退出程序")print("help 查询帮助手册")passdef main():database = shelve.open('data_people', writeback=True)try:while True:cmd = enter_cmd()if cmd == 'add':store_person(database)elif cmd == 'del':del_person(database)elif cmd == 'update':update_person(database)elif cmd == 'search':search_person(database)elif cmd == 'print_all':print_all(database)elif cmd == 'help':print_help()elif cmd == 'quit':returnfinally:database.close()if __name__ == '__main__':main()# 运行结果请输入命令:help命令:add 增加一个人的数据del 删除一个人的数据update 修改一个人的数据search 查询一个人的数据print_all 打印所有数据quit 退出程序help 查询帮助手册请输入命令:print_all打印所有数据:01 {'name': 'zhangsan', 'age': '18', 'phone': '101010101'}请输入命令:add请输入一个id:02请输入姓名:lisi请输入年龄:20请输入电话号码:2020202002存储信息:pid是02,存储的内容是{'name': 'lisi', 'age': '20', 'phone': '2020202002'}请输入命令:del请输入要删除的id:02pid 02的数据已被删除请输入命令:search请输入要查询的id:01请输入要查询的信息:(name、age、phone)namepid 01 的 name 是 zhangsan请输入命令:update请输入要修改的id:01请输入要修改的信息:(name、age、phone)phone请输入新的内容:20202020pid 01 的 phone 修改为了20202020请输入命令:print_all打印所有数据:01 {'name': 'zhangsan', 'age': '18', 'phone': '20202020'}请输入命令:quit
hashlib模块
python的hashlib模块提供了常见的摘要算法,如MD5、SHA1等。
摘要算法就是通过一个摘要函数 f() 把任意长度的一条数据 data 计算为一个固定长度的数据 str(通常是十六进制字符串)。
摘要算法的目的是发现原来的数据是否被篡改过。原理是摘要函数是一个单向函数,由 f(data)算出 str 很容易,但是想由 str 反推 data 却十分困难。而且对原数据data哪怕做一bit的修改都会导致计算出来的摘要完全不同。
import hashlibmd5 = hashlib.md5()data = "how to use md5 in python hashlib?"md5.update(data.encode('utf-8'))print(md5.hexdigest())# 运行结果d26a53750bc40b38b65a520292f69306
如果数据量很大,可以分块多次调用update,最后计算的结果是一样的。
import hashlibmd5 = hashlib.md5()md5.update('how to use md5 '.encode('utf-8'))md5.update('in python hashlib?'.encode('utf-8'))print(md5.hexdigest())# 运行结果d26a53750bc40b38b65a520292f69306
MD5是最常见的摘要算法,生成结果是固定的128位(二进制),输出的通常是32位字符串(16进制)。另一种常见的摘要算法就是SHA1,它的调用与MD5完全类似。
import hashlibsha1 = hashlib.sha1()sha1.update('how to use md5 '.encode('utf-8'))sha1.update('in python hashlib?'.encode('utf-8'))print(sha1.hexdigest())# 运行结果b752d34ce353e2916e943dc92501021c8f6bca8c
摘要算法应用
在允许用户登录的网站都会存储用户登录的用户名和密码等信息,通常是把数据存到数据库表中。
name | password--------+----------michael | 123456bob | abc999alice | alice2008
如果用MD5来保护密码就是这样:
username | password---------+---------------------------------michael | e10adc3949ba59abbe56e057f20f883ebob | 878ef96e86145580c38c87f0410ad153alice | 99b1c2188db85afee403b1536010c2c9
然而有很多MD5碰撞工具,可以轻松将密码碰撞出来。
所以为了确保密码不会轻松暴露,通常会加入一个用户口令,即通过对原始数据加上一个复杂字符串,这个操作俗称“加盐”。我个人理解就像输入密码后,还要求输入一个验证码,密码加验证码一起进行MD5加密,这样即使通过MD5反推出明文,只要黑客不知道Sault也不知道密码是什么。
另外如果两个用户存储的密码都使用123456这样简单的口令,在数据库里就存了两条相同的MD5值,为了避免这个情况,也可以用加盐的方法来解决。比如假定用户无法修改登录名,就可以通过把登录名作为Salt的一部分来计算MD5,从而实现相同口令的用户也存储不同的MD5。
# md5加密import hashlibuser_name = "zhangsan"user_password = "123456"# 获取md5对象md5 = hashlib.md5()# 对用户名和密码进行md5加密md5.update((user_name + user_password).encode('utf-8'))salt_password = md5.hexdigest()print(salt_password)dict1 = {user_name, salt_password}login_name = input("请输入用户名:")login_password = input("请输入密码:")md5_login = hashlib.md5()# md5.update((login_name + login_password).encode('utf-8'))md5_login.update((login_name + login_password).encode('utf-8'))md5_pwd = md5.hexdigest()print(md5_pwd)if md5_pwd == salt_password:print("登录成功")else:print("登录失败")
需要注意的是,我一开始用md5 = hashlib.md5()获取了一个md5对象,然后用这个md5对象对数据库中存储的用户名user_name和密码user_password进行加密,接着我想继续用这个md5对象对下面用户输入的login_name和login_password进行加密,结果发现即使我输入正确,两次加密结果却不同。
可见参考链接:https://blog.csdn.net/YyangWwei/article/details/105100505
简单来说,就是每调用一次hashlib.md5()函数就要重新实例化一次对象,否则输入输出就不相匹配。原因是同一个md5对象如果多次调用hashlib.md5()函数,后一次调用是将历史输入与本次输入做拼接来运算,比如连续对“123”作两次md5运算,第二次的结果是与前一次输入拼接后的运算结果,即“123123”的运算结果。
import hashlibmd5 = hashlib.md5()md5.update('123'.encode('utf-8'))print(md5.hexdigest())md5.update('123'.encode('utf-8'))print(md5.hexdigest())md5_new = hashlib.md5()md5_new.update('123123'.encode('utf-8'))print(md5_new.hexdigest())# 运行结果202cb962ac59075b964b07152d234b704297f44b13955235245b2497399d7a934297f44b13955235245b2497399d7a93
