day 25 序列化模块、hashlib模块
什么叫序列化——将原本的字典、列表等内容转换成一个字符串的过程就叫做序列化。
1 比如,我们在python代码中计算的一个数据需要给另外一段程序使用,那我们怎么给? 2 现在我们能想到的方法就是存在文件里,然后另一个python程序再从文件里读出来。 3 但是我们都知道,对于文件来说是没有字典这个概念的,所以我们只能将数据转换成字典放到文件中。 4 你一定会问,将字典转换成一个字符串很简单,就是str(dic)就可以办到了,为什么我们还要学习序列化模块呢? 5 没错序列化的过程就是从dic 变成str(dic)的过程。现在你可以通过str(dic),将一个名为dic的字典转换成一个字符串, 6 但是你要怎么把一个字符串转换成字典呢? 7 聪明的你肯定想到了eval(),如果我们将一个字符串类型的字典str_dic传给eval,就会得到一个返回的字典类型了。 8 eval()函数十分强大,但是eval是做什么的?e官方demo解释为:将字符串str当成有效的表达式来求值并返回计算结果。 9 BUT!强大的函数有代价。安全性是其最大的缺点。 10 想象一下,如果我们从文件中读出的不是一个数据结构,而是一句"删除文件"类似的破坏性语句,那么后果实在不堪设设想。 11 而使用eval就要担这个风险。 12 所以,我们并不推荐用eval方法来进行反序列化操作(将str转换成python中的数据结构)
序列化的目的
1、以某种存储形式使自定义对象持久化;
json
Json模块提供了四个功能:dumps、dump、loads、load
1 import json 2 dic = {'k1':'v1','k2':'v2','k3':'v3'} 3 str_dic = json.dumps(dic) #序列化:将一个字典转换成一个字符串 4 print(type(str_dic),str_dic) #<class 'str'> {"k3": "v3", "k1": "v1", "k2": "v2"} 5 #注意,json转换完的字符串类型的字典中的字符串是由""表示的 6 7 dic2 = json.loads(str_dic) #反序列化:将一个字符串格式的字典转换成一个字典 8 #注意,要用json的loads功能处理的字符串类型的字典中的字符串必须由""表示 9 print(type(dic2),dic2) #<class 'dict'> {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'} 10 11 12 list_dic = [1,['a','b','c'],3,{'k1':'v1','k2':'v2'}] 13 str_dic = json.dumps(list_dic) #也可以处理嵌套的数据类型 14 print(type(str_dic),str_dic) #<class 'str'> [1, ["a", "b", "c"], 3, {"k1": "v1", "k2": "v2"}] 15 list_dic2 = json.loads(str_dic) 16 print(type(list_dic2),list_dic2) #<class 'list'> [1, ['a', 'b', 'c'], 3, {'k1': 'v1', 'k2': 'v2'}]
1 import json 2 f = open('json_file','w') 3 dic = {'k1':'v1','k2':'v2','k3':'v3'} 4 json.dump(dic,f) #dump方法接收一个文件句柄,直接将字典转换成json字符串写入文件 5 f.close() 6 7 f = open('json_file') 8 dic2 = json.load(f) #load方法接收一个文件句柄,直接将文件中的json字符串转换成数据结构返回 9 f.close() 10 print(type(dic2),dic2)
1 import json 2 f = open('file','w') 3 json.dump({'国籍':'中国'},f) 4 ret = json.dumps({'国籍':'中国'}) 5 f.write(ret+'\n') 6 json.dump({'国籍':'美国'},f,ensure_ascii=False) 7 ret = json.dumps({'国籍':'美国'},ensure_ascii=False) 8 f.write(ret+'\n') 9 f.close()
1 Serialize obj to a JSON formatted str.(字符串表示的json对象) 2 Skipkeys:默认值是False,如果dict的keys内的数据不是python的基本类型(str,unicode,int,long,float,bool,None),设置为False时,就会报TypeError的错误。此时设置成True,则会跳过这类key 3 ensure_ascii:,当它为True的时候,所有非ASCII码字符显示为\uXXXX序列,只需在dump时将ensure_ascii设置为False即可,此时存入json的中文即可正常显示。) 4 If check_circular is false, then the circular reference check for container types will be skipped and a circular reference will result in an OverflowError (or worse). 5 If allow_nan is false, then it will be a ValueError to serialize out of range float values (nan, inf, -inf) in strict compliance of the JSON specification, instead of using the JavaScript equivalents (NaN, Infinity, -Infinity). 6 indent:应该是一个非负的整型,如果是0就是顶格分行显示,如果为空就是一行最紧凑显示,否则会换行且按照indent的数值显示前面的空白分行显示,这样打印出来的json数据也叫pretty-printed json 7 separators:分隔符,实际上是(item_separator, dict_separator)的一个元组,默认的就是(‘,’,’:’);这表示dictionary内keys之间用“,”隔开,而KEY和value之间用“:”隔开。 8 default(obj) is a function that should return a serializable version of obj or raise TypeError. The default simply raises TypeError. 9 sort_keys:将数据根据keys的值进行排序。 10 To use a custom JSONEncoder subclass (e.g. one that overrides the .default() method to serialize additional types), specify it with the cls kwarg; otherwise JSONEncoder is used.
1 import json 2 data = {'username':['李华','二愣子'],'sex':'male','age':16} 3 json_dic2 = json.dumps(data,sort_keys=True,indent=2,separators=(',',':'),ensure_ascii=False) 4 print(json_dic2)
pickle
json & pickle 模块
用于序列化的两个模块
- json,用于字符串 和 python数据类型间进行转换
- pickle,用于python特有的类型 和 python的数据类型间进行转换
pickle模块提供了四个功能:dumps、dump(序列化,存)、loads(反序列化,读)、load (不仅可以序列化字典,列表...可以把python中任意的数据类型序列化)
1 import pickle 2 dic = {'k1':'v1','k2':'v2','k3':'v3'} 3 str_dic = pickle.dumps(dic) 4 print(str_dic) #一串二进制内容 5 6 dic2 = pickle.loads(str_dic) 7 print(dic2) #字典 8 9 import time 10 struct_time = time.localtime(1000000000) 11 print(struct_time) 12 f = open('pickle_file','wb') 13 pickle.dump(struct_time,f) 14 f.close() 15 16 f = open('pickle_file','rb') 17 struct_time2 = pickle.load(f) 18 print(struct_time2.tm_year)
这时候机智的你又要说了,既然pickle如此强大,为什么还要学json呢?
这里我们要说明一下,json是一种所有的语言都可以识别的数据结构。
如果我们将一个字典或者序列化成了一个json存在文件里,那么java代码或者js代码也可以拿来用。
但是如果我们用pickle进行序列化,其他语言就不能读懂这是什么了~
所以,如果你序列化的内容是列表或者字典,我们非常推荐你使用json模块
但如果出于某种原因你不得不序列化其他的数据类型,而未来你还会用python对这个数据进行反序列化的话,那么就可以使用pickle
shelve
shelve也是python提供给我们的序列化工具,比pickle用起来更简单一些。
shelve只提供给我们一个open方法,是用key来访问的,使用起来和字典类似。
1 import shelve 2 f = shelve.open('shelve_file') 3 f['key'] = {'int':10, 'float':9.5, 'string':'Sample data'} #直接对文件句柄操作,就可以存入数据 4 f.close() 5 6 import shelve 7 f1 = shelve.open('shelve_file') 8 existing = f1['key'] #取出数据的时候也只需要直接用key获取即可,但是如果key不存在会报错 9 f1.close() 10 print(existing)
这个模块有个限制,它不支持多个应用同一时间往同一个DB进行写操作。所以当我们知道我们的应用如果只进行读操作,我们可以让shelve通过只读方式打开DB
1 import shelve 2 f = shelve.open('shelve_file', flag='r') 3 existing = f['key'] 4 f.close() 5 print(existing
由于shelve在默认情况下是不会记录待持久化对象的任何修改的,所以我们在shelve.open()时候需要修改默认参数,否则对象的修改不会保存。
1 import shelve 2 f1 = shelve.open('shelve_file') 3 print(f1['key']) 4 f1['key']['new_value'] = 'this was not here before' 5 f1.close() 6 7 f2 = shelve.open('shelve_file', writeback=True) 8 print(f2['key']) 9 f2['key']['new_value'] = 'this was not here before' 10 f2.close() 11 12 设置writeback
writeback方式有优点也有缺点。优点是减少了我们出错的概率,并且让对象的持久化对用户更加的透明了;但这种方式并不是所有的情况下都需要,首先,使用writeback以后,shelf在open()的时候会增加额外的内存消耗,并且当DB在close()的时候会将缓存中的每一个对象都写入到DB,这也会带来额外的等待时间。因为shelve没有办法知道缓存中哪些对象修改了,哪些对象没有修改,因此所有的对象都会被写入。
hashlib模块
算法介绍
Python的hashlib提供了常见的摘要算法,如MD5,SHA1等等。
什么是摘要算法呢?摘要算法又称哈希算法、散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。
摘要算法就是通过摘要函数f()对任意长度的数据data计算出固定长度的摘要digest,目的是为了发现原始数据是否被人篡改过。
摘要算法之所以能指出数据是否被篡改过,就是因为摘要函数是一个单向函数,计算f(data)很容易,但通过digest反推data却非常困难。而且,对原始数据做一个bit的修改,都会导致计算出的摘要完全不同。
我们以常见的摘要算法MD5为例,计算出一个字符串的MD5值:
1 import hashlib 2 3 md5 = hashlib.md5() 4 md5.update('how to use md5 in python hashlib?') 5 print md5.hexdigest() 6 7 计算结果如下: 8 d26a53750bc40b38b65a520292f69306
如果数据量很大,可以分块多次调用update(),最后计算的结果是一样的:
md5 = hashlib.md5() md5.update('how to use md5 in ') md5.update('python hashlib?') print md5.hexdigest()
MD5是最常见的摘要算法,速度很快,生成结果是固定的128 bit字节,通常用一个32位的16进制字符串表示。另一种常见的摘要算法是SHA1,调用SHA1和调用MD5完全类似:
1 import hashlib 2 3 sha1 = hashlib.sha1() 4 sha1.update('how to use sha1 in ') 5 sha1.update('python hashlib?') 6 print sha1.hexdigest()
SHA1的结果是160 bit字节,通常用一个40位的16进制字符串表示。比SHA1更安全的算法是SHA256和SHA512,不过越安全的算法越慢,而且摘要长度更长。
摘要算法应用
任何允许用户登录的网站都会存储用户登录的用户名和口令。如何存储用户名和口令呢?方法是存到数据库表中:
1 name | password 2 --------+---------- 3 michael | 123456 4 bob | abc999 5 alice | alice2008
考虑这么个情况,很多用户喜欢用123456,888888,password这些简单的口令,于是,黑客可以事先计算出这些常用口令的MD5值,得到一个反推表:
1 'e10adc3949ba59abbe56e057f20f883e': '123456' 2 '21218cca77804d2ba1922c33e0151105': '888888' 3 '5f4dcc3b5aa765d61d8327deb882cf99': 'password'
这样,无需破解,只需要对比数据库的MD5,黑客就获得了使用常用口令的用户账号。
对于用户来讲,当然不要使用过于简单的口令。但是,我们能否在程序设计上对简单口令加强保护呢?
由于常用口令的MD5值很容易被计算出来,所以,要确保存储的用户口令不是那些已经被计算出来的常用口令的MD5,这一方法通过对原始口令加一个复杂字符串来实现,俗称“加盐”:
1 hashlib.md5("salt".encode("utf8"))
经过Salt处理的MD5口令,只要Salt不被黑客知道,即使用户输入简单口令,也很难通过MD5反推明文口令。
但是如果有两个用户都使用了相同的简单口令比如123456,在数据库中,将存储两条相同的MD5值,这说明这两个用户的口令是一样的。有没有办法让使用相同口令的用户存储不同的MD5呢?
如果假定用户无法修改登录名,就可以通过把登录名作为Salt的一部分来计算MD5,从而实现相同口令的用户也存储不同的MD5。
摘要算法在很多地方都有广泛的应用。要注意摘要算法不是加密算法,不能用于加密(因为无法通过摘要反推明文),只能用于防篡改,但是它的单向计算特性决定了可以在不存储明文口令的情况下验证用户口令。
1 # py文件就是模块 2 # python之所以好用 模块多 3 # 三种 4 # 内置模块 python安装的时候自带的 5 # 扩展模块 itchat # 别人写好的 需要安装之后可以直接使用 6 # beautiful soap 7 # selenium 网页自动化测试工具 8 # django tornado 9 # 自定义模块 自己写的模块 10 11 # 内置模块 12 # 序列化模块 13 # hashlib模块 14 15 # 序列化模块 16 # 能存储在文件中的一定是字符串 或者是 字节 17 # 能在网络上传输的 只有字节 18 # dic = {"大表哥":(190,90)} # python 19 # print(str(dic)) 20 # print(str(dic).encode(encoding='utf-8')) 21 # b"{'\xe5\xa4\xa7\xe8\xa1\xa8\xe5\x93\xa5': (190, 90)}" 22 # s = b"{'\xe5\xa4\xa7\xe8\xa1\xa8\xe5\x93\xa5': (190, 90)}" 23 # ret = s.decode(encoding='utf-8') 24 # print(ret) 25 # print(eval(ret)) 26 27 # dic = {"大表哥":(190,90)} 28 # dic --> 字符串 # 序列化 29 # 字符串 --> dic # 反序列化 30 # 序列化 == 创造一个序列 ==》创造一个字符串 31 # 实例化 == 创造一个实例 32 33 # python中的序列化模块 34 # json 所有的编程语言都通用的序列化格式 35 # 它支持的数据类型非常有限 数字 字符串 列表 字典 36 # pickle 只能在python语言的程序之间传递数据用的 37 # pickle支持python中所有的数据类型 38 # shelve python3.* 之后才有的 39 40 import json 41 # dic = {"大表哥":(190,90,'捏脚')} 42 # 序列化 43 # ret = json.dumps(dic,ensure_ascii=False) 44 # print(type(dic),dic) 45 # print(type(ret),ret) 46 # print('*',str(dic)) # 土办法 47 # 反序列化 48 # res = json.loads(ret) 49 # print(type(res),res) 50 51 # dump和load 是直接将对象序列化之后写入文件 52 # 依赖一个文件句柄 53 # dic = {"大表哥":(190,90,'捏脚')} 54 # f = open('大表哥','w',encoding='utf-8') 55 # json.dump(dic,f,ensure_ascii=False) # 先接收要序列化的对象 再接受文件句柄 56 # f.close() 57 58 # f = open('大表哥','r',encoding='utf-8') 59 # ret = json.load(f) 60 # print(type(ret),ret) 61 62 # data = {'username':['李华','二愣子'],'sex':'male','age':16} 63 # json_dic2 = json.dumps(data,sort_keys=True,indent=4,separators=(',',':'),ensure_ascii=False) 64 # print(json_dic2) 65 66 # 3个字典 67 # dic1 = {"大表哥":(190,90,'捏脚')} 68 # dic2 = {"2表哥":(190,90,'捏脚')} 69 # dic3 = {"3表哥":(190,90,'捏脚')} 70 # str1 = json.dumps(dic1) 71 # f = open('大表哥','a',encoding='utf-8') 72 # str1 = json.dumps(dic1) 73 # f.write(str1+'\n') 74 # str2 = json.dumps(dic2) 75 # f.write(str2+'\n') 76 # str3 = json.dumps(dic3) 77 # f.write(str3+'\n') 78 # f.close() 79 80 # f = open('大表哥','r',encoding='utf-8') 81 # for line in f: 82 # print(json.loads(line.strip())) 83 # f.close() 84 85 # dumps序列化 loads反序列化 只在内存中操作数据 主要用于网络传输 和多个数据与文件打交道 86 # dump序列化 load反序列化 主要用于一个数据直接存在文件里—— 直接和文件打交道 87 # 参数 88 89 # import json 90 # dic = {(190,90,'捏脚'):"大表哥"} # json不支持元组 不支持除了str数据类型之外的key 91 # print(json.dumps(dic)) 92 93 import pickle 94 # dic = {(190,90,'捏脚'):"大表哥"} 95 # ret = pickle.dumps(dic) # 序列化结果 不是一个可读的字符串 而是一个bytes类型 96 # print(ret) 97 # print(pickle.loads(ret)) 98 99 # dic = {(190,90,'捏脚'):"大表哥"} 100 # f = open('大表哥2','wb') # 使用pickle dump必须以+b的形式打开文件 101 # pickle.dump(dic,f) 102 # f.close() 103 104 # f = open('大表哥2','rb') 105 # print(pickle.load(f)) 106 # f.close() 107 108 import pickle 109 # 关于写多行 110 # dic1 = {"大表哥":(190,90,'捏脚')} 111 # dic2 = {"2表哥":(190,90,'捏脚')} 112 # dic3 = {"3表哥":(190,90,'捏脚')} 113 # f = open('大表哥3','wb') 114 # pickle.dump(dic1,f) 115 # pickle.dump(dic2,f) 116 # pickle.dump(dic3,f) 117 # f.close() 118 119 # 读写入的多行 120 # f = open('大表哥3','rb') 121 # while True: 122 # try: 123 # print(pickle.load(f)) 124 # except EOFError: 125 # break 126 127 # json 在写入多次dump的时候 不能对应执行多次load来取出数据,pickle可以 128 # json 如果要写入多个元素 可以先将元素dumps序列化,f.write(序列化+'\n')写入文件 129 # 读出元素的时候,应该先按行读文件,在使用loads将读出来的字符串转换成对应的数据类型 130 131 # 关于序列化自定义类的对象 132 # class A: 133 # def __init__(self,name,age): 134 # self.name=name 135 # self.age=age 136 # a = A('alex',80) 137 # import json 138 # json.dumps(a) 139 # import pickle 140 # ret = pickle.dumps(a) 141 # print(ret) 142 # obj = pickle.loads(ret) 143 # print(obj.__dict__) 144 145 # import pickle 146 # f = open('大侄子1','wb') 147 # pickle.dump(a,f) 148 # f.close() 149 # f = open('大侄子1','rb') 150 # obj = pickle.load(f) 151 # print(obj.__dict__)
1 # f.close() 2 # print(existing) 3 4 # import shelve 5 # f = shelve.open('shelve_file', flag='r') 6 # # f['key']['int'] = 50 # 不能修改已有结构中的值 7 # # f['key']['new'] = 'new' # 不能在已有的结构中添加新的项 8 # f['key'] = 'new' # 但是可以覆盖原来的结构 9 # f.close() 10 # # 11 # import shelve 12 # f1 = shelve.open('shelve_file') 13 # existing = f1['key'] #取出数据的时候也只需要直接用key获取即可,但是如果key不存在会报错 14 # f1.close() 15 # print(existing) 16 17 # import shelve 18 # f1 = shelve.open('shelve_file') 19 # print(f1['key']) 20 # f1['key']['new_value'] = 'this was not here before' 21 # f1.close() 22 23 # import shelve 24 # f1 = shelve.open('shelve_file') 25 # existing = f1['key'] #取出数据的时候也只需要直接用key获取即可,但是如果key不存在会报错 26 # f1.close() 27 # print(existing) 28 29 # import shelve 30 # f2 = shelve.open('shelve_file', writeback=True) 31 # print(f2['key']) 32 # f2['key']['new_value'] = 'this was not here before' 33 # f2.close() 34 # 35 # f1 = shelve.open('shelve_file') 36 # existing = f1['key'] #取出数据的时候也只需要直接用key获取即可,但是如果key不存在会报错 37 # f1.close() 38 # print(existing)
1 # hash 哈希算法 可hash数据类型——>数字的过程 2 3 # hashlib —— 摘要算法 4 # 也是一些算法的集合,有好多算法 5 # 字符串 --> 数字 6 # 不同的字符串 --> 数字一定不同 7 # 无论在哪台机器上,在什么时候计算,对相同的字符串结果总是一样的 8 # 摘要过程不可逆 9 10 # 用法 11 # 文件的一致性校验 12 # 密文验证的时候加密 13 14 # 密文验证的时候加密 15 import hashlib 16 # md5算法 通用的算法 17 # sha算法 安全系数更高,sha算法有很多种,后面的数字越大安全系数越高, 18 # 得到的数字结果越长,计算的时间越长 19 m = hashlib.md5() 20 m.update('alex3714'.encode('utf-8')) 21 print(m.hexdigest()) 22 23 m = hashlib.md5() 24 m.update('dazhizi sha'.encode('utf-8')) 25 print(m.hexdigest()) 26 27 m = hashlib.md5() 28 m.update('123456'.encode('utf-8')) 29 print(m.hexdigest()) 30 31 32 # 将所有常见的密码 md5摘要 33 # 密码 摘要结果 34 # 暴力破解 和 撞库 35 36 # 加盐 37 m = hashlib.md5('wahaha'.encode('utf-8')) 38 m.update('123456'.encode('utf-8')) 39 print(m.hexdigest()) # d1c59b7f2928f9b1d63898133294ad2c 40 41 # 123456 42 m = hashlib.md5('wahaha'.encode('utf-8')) 43 m.update('123456'.encode('utf-8')) 44 print(m.hexdigest()) 45 46 # 动态加盐 47 # 500 用户名 和 密码 48 # 123456 49 # 111111 d1c59b7f2928f9b1d63898133294ad2c 50 # pwd username 51 username = 'alex' 52 m = hashlib.md5(username[:2:2].encode('utf-8')) 53 m.update('123456'.encode('utf-8')) 54 print(m.hexdigest()) # d1c59b7f2928f9b1d63898133294ad2c
1 # 写一个类 定义100个对象 2 # 拥有三个属性 name age sex 3 # 如果两个对象的name 和 sex完全相同 4 # 我们就认为这是一个对象 5 # 忽略age属性 6 # 做这100个对象的去重工作 7 class Person: 8 def __init__(self,name,age,sex): 9 self.name = name 10 self.age = age 11 self.sex = sex 12 def __hash__(self): 13 # hash算法本身就存在了 且直接在python中就能调用 14 # 姓名相同 性别相同的对象的hash值应该相等才行 15 # 姓名性别都是字符串 16 return hash(self.name+self.sex) 17 def __eq__(self, other): 18 if self.name == other.name and self.sex == other.sex: 19 return True 20 # python2.7 21 # 去重 set 22 # hash方法 23 # set([obj,obj]) unhashable 24 # hash算法 一个值 进行一系列的计算得出一个数字在一次程序执行中总是不变 25 #来让每一个不同的值计算出的数字都不相等 26 obj_lst = [] 27 obj_lst.append(Person('alex',80,'male')) 28 obj_lst.append(Person('alex',70,'male')) 29 obj_lst.append(Person('alex',60,'male')) 30 obj_lst.append(Person('boss_jin',50,'male')) 31 obj_lst.append(Person('boss_jin',40,'male')) 32 obj_lst.append(Person('boss_jin',30,'male')) 33 obj_lst.append(Person('nezha',20,'male')) 34 obj_lst.append(Person('nezha',10,'male')) 35 obj_lst = set(obj_lst) 36 for obj in obj_lst:print(obj.name) 37 # set对一个对象序列的去重 依赖于这个对象的两个方法 hash eq 38 39 40 # 可hash顺带着写的 41 # eq来做判断 42 43 # key hash 数字 --》 内存地址 --》 value 44 # set hash 数字 --》 内存地址 --》 set中的元素 45 # 'aaa' hash 46 47 # java 48 # set去重 一个容器中 有相同值的内容 __eq__ 49 # 当你这个容器中有10000个元素的时候 我判断第10000个元素 50 # hash算法 51 # 'abc' 52 # 'bca' 53 # set对一个对象序列的去重 如何判断这两个值是否相等 54 # 值a进行hash --> 存值 55 # 值b进行hash --> 判断值是否相等 -相等-> 说明是一样的 56 #-不相等-> 在开辟一个空间 来存放b






浙公网安备 33010602011771号