python序列化及加密算法的使用
一 序列化模块
什么叫序列化——将原本的字典、列表等内容转换成一个字符串的过程就叫做序列化。
比如,我们在python代码中计算的一个数据需要给另外一段程序使用,那我们怎么给?
现在我们能想到的方法就是存在文件里,然后另一个python程序再从文件里读出来。
但是我们都知道,对于文件来说是没有字典这个概念的,所以我们只能将数据转换成字典放到文件中。
你一定会问,将字典转换成一个字符串很简单,就是str(dic)就可以办到了,为什么我们还要学习序列化模块呢?
没错序列化的过程就是从dic 变成str(dic)的过程。现在你可以通过str(dic),将一个名为dic的字典转换成一个字符串,
但是你要怎么把一个字符串转换成字典呢?
聪明的你肯定想到了eval(),如果我们将一个字符串类型的字典str_dic传给eval,就会得到一个返回的字典类型了。
eval()函数十分强大,但是eval是做什么的?e官方demo解释为:将字符串str当成有效的表达式来求值并返回计算结果。
BUT!强大的函数有代价。安全性是其最大的缺点。
想象一下,如果我们从文件中读出的不是一个数据结构,而是一句"删除文件"类似的破坏性语句,那么后果实在不堪设设想。
而使用eval就要担这个风险。
所以,我们并不推荐用eval方法来进行反序列化操作(将str转换成python中的数据结构)
序列化的目的
1、以某种存储形式使自定义对象持久化;
2、将对象从一个地方传递到另一个地方。
3、使程序更具维护性。
Python可序列化的数据类型:
| Python | JSON |
| dict | object |
| list, tuple | array |
| str | string |
| int, float | number |
| True | true |
| False | false |
| None | null |
1.1 json模块
Json模块提供了四个功能:dumps、dump、loads、load
import json
dic = {'k1':'v1','k2':'v2','k3':'v3'}
str_dic = json.dumps(dic) #序列化:将一个字典转换成一个字符串
print(type(str_dic),str_dic) #<class 'str'> {"k3": "v3", "k1": "v1", "k2": "v2"}
#注意,json转换完的字符串类型的字典中的字符串是由""表示的
dic2 = json.loads(str_dic) #反序列化:将一个字符串格式的字典转换成一个字典
#注意,要用json的loads功能处理的字符串类型的字典中的字符串必须由""表示
print(type(dic2),dic2) #<class 'dict'> {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
list_dic = [1,['a','b','c'],3,{'k1':'v1','k2':'v2'}]
str_dic = json.dumps(list_dic) #也可以处理嵌套的数据类型
print(type(str_dic),str_dic) #<class 'str'> [1, ["a", "b", "c"], 3, {"k1": "v1", "k2": "v2"}]
list_dic2 = json.loads(str_dic)
print(type(list_dic2),list_dic2) #<class 'list'> [1, ['a', 'b', 'c'], 3, {'k1': 'v1', 'k2': 'v2'}]
loads和dumps
dumps loads
import json
f = open('json_file','w')
dic = {'k1':'v1','k2':'v2','k3':'v3'}
json.dump(dic,f) #dump方法接收一个文件句柄,直接将字典转换成json字符串写入文件
f.close()
f = open('json_file')
dic2 = json.load(f) #load方法接收一个文件句柄,直接将文件中的json字符串转换成数据结构返回
f.close()
print(type(dic2),dic2)
其它参数说明:
Serialize obj to a JSON formatted str.(字符串表示的json对象)
Skipkeys:默认值是False,如果dict的keys内的数据不是python的基本类型(str,unicode,int,long,float,bool,None),设置为False时,就会报TypeError的错误。此时设置成True,则会跳过这类key
ensure_ascii:,当它为True的时候,所有非ASCII码字符显示为\uXXXX序列,只需在dump时将ensure_ascii设置为False即可,此时存入json的中文即可正常显示。)
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).
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).
indent:应该是一个非负的整型,如果是0就是顶格分行显示,如果为空就是一行最紧凑显示,否则会换行且按照indent的数值显示前面的空白分行显示,这样打印出来的json数据也叫pretty-printed json
separators:分隔符,实际上是(item_separator, dict_separator)的一个元组,默认的就是(‘,’,’:’);这表示dictionary内keys之间用“,”隔开,而KEY和value之间用“:”隔开。
default(obj) is a function that should return a serializable version of obj or raise TypeError. The default simply raises TypeError.
sort_keys:将数据根据keys的值进行排序。
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.
import json
data = {'username':['李华','二愣子'],'sex':'male','age':16}
json_dic2 = json.dumps(data,sort_keys=True,indent=2,separators=(',',':'),ensure_ascii=False)
print(json_dic2)
1.2 pickle模块
用于序列化的两个模块
json,用于字符串 和 python数据类型间进行转换
pickle,用于python特有的类型 和 python的数据类型间进行转换
pickle模块提供了四个功能:dumps、dump(序列化,存)、loads(反序列化,读)、load (不仅可以序列化字典,列表...可以把python中任意的数据类型序列化)
pickle
import pickle
dic = {'k1':'v1','k2':'v2','k3':'v3'}
str_dic = pickle.dumps(dic)
print(str_dic) #一串二进制内容
dic2 = pickle.loads(str_dic)
print(dic2) #字典
import time
struct_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)
这时候机智的你又要说了,既然pickle如此强大,为什么还要学json呢?
这里我们要说明一下,json是一种所有的语言都可以识别的数据结构。
如果我们将一个字典或者序列化成了一个json存在文件里,那么java代码或者js代码也可以拿来用。
但是如果我们用pickle进行序列化,其他语言就不能读懂这是什么了~
所以,如果你序列化的内容是列表或者字典,我们非常推荐你使用json模块
但如果出于某种原因你不得不序列化其他的数据类型,而未来你还会用python对这个数据进行反序列化的话,那么就可以使用pickle
二 hashlib模块
算法介绍
Python的hashlib提供了常见的摘要算法,如MD5,SHA1等等。
什么是摘要算法呢?摘要算法又称哈希算法、散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。
摘要算法就是通过摘要函数f()对任意长度的数据data计算出固定长度的摘要digest,目的是为了发现原始数据是否被人篡改过。
摘要算法之所以能指出数据是否被篡改过,就是因为摘要函数是一个单向函数,计算f(data)很容易,但通过digest反推data却非常困难。而且,对原始数据做一个bit的修改,都会导致计算出的摘要完全不同。
我们以常见的摘要算法MD5为例,计算出一个字符串的MD5值:
import hashlib
md5 = hashlib.md5()
md5.update('how to use md5 in python hashlib?')
print md5.hexdigest()
计算结果如下:
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完全类似:
import hashlib
sha1 = hashlib.sha1()
sha1.update('how to use sha1 in ')
sha1.update('python hashlib?')
print sha1.hexdigest()
SHA1的结果是160 bit字节,通常用一个40位的16进制字符串表示。比SHA1更安全的算法是SHA256和SHA512,不过越安全的算法越慢,而且摘要长度更长。
摘要算法应用
任何允许用户登录的网站都会存储用户登录的用户名和口令。如何存储用户名和口令呢?方法是存到数据库表中:
name | password
--------+----------
michael | 123456
bob | abc999
alice | alice2008
如果以明文保存用户口令,如果数据库泄露,所有用户的口令就落入黑客的手里。此外,网站运维人员是可以访问数据库的,也就是能获取到所有用户的口令。正确的保存口令的方式是不存储用户的明文口令,而是存储用户口令的摘要,比如MD5:
username | password
---------+---------------------------------
michael | e10adc3949ba59abbe56e057f20f883e
bob | 878ef96e86145580c38c87f0410ad153
alice | 99b1c2188db85afee403b1536010c2c9
考虑这么个情况,很多用户喜欢用123456,888888,password这些简单的口令,于是,黑客可以事先计算出这些常用口令的MD5值,得到一个反推表:
'e10adc3949ba59abbe56e057f20f883e': '123456'
'21218cca77804d2ba1922c33e0151105': '888888'
'5f4dcc3b5aa765d61d8327deb882cf99': 'password'
这样,无需破解,只需要对比数据库的MD5,黑客就获得了使用常用口令的用户账号。
对于用户来讲,当然不要使用过于简单的口令。但是,我们能否在程序设计上对简单口令加强保护呢?
由于常用口令的MD5值很容易被计算出来,所以,要确保存储的用户口令不是那些已经被计算出来的常用口令的MD5,这一方法通过对原始口令加一个复杂字符串来实现,俗称“加盐”:
hashlib.md5("salt".encode("utf8"))
经过Salt处理的MD5口令,只要Salt不被黑客知道,即使用户输入简单口令,也很难通过MD5反推明文口令。
但是如果有两个用户都使用了相同的简单口令比如123456,在数据库中,将存储两条相同的MD5值,这说明这两个用户的口令是一样的。有没有办法让使用相同口令的用户存储不同的MD5呢?
如果假定用户无法修改登录名,就可以通过把登录名作为Salt的一部分来计算MD5,从而实现相同口令的用户也存储不同的MD5。
摘要算法在很多地方都有广泛的应用。要注意摘要算法不是加密算法,不能用于加密(因为无法通过摘要反推明文),只能用于防篡改,但是它的单向计算特性决定了可以在不存储明文口令的情况下验证用户口令。
三 import的使用
import 模块 先要怎么样?
import tbjx 执行一次tbjx这个模块里面的所有代码,
第一次引用tbjx这个模块,会将这个模块里面的所有代码加载到内存,只要你的程序没有结束,接下来你在
引用多少次,它会先从内存中寻找有没有此模块,如果已经加载到内存,就不再重复加载.
第一次导入模块执行三件事 ***
1. 在内存中创建一个以tbjx命名的名称空间.
2. 执行此名称空间所有的可执行代码(将tbjx.py文件中所有的变量与值的对应关系加载到这
个名称空间).
3. 通过tbjx. 的方式引用模块里面的代码.
import tbjx
print(tbjx.name)
tbjx.read1()
被导入模块有独立的名称空间 ***
import tbjx
# name = 'alex'
# print(name)
# print(tbjx.name)
#
# def read1():
# print(666)
# tbjx.read1()
#
# name = '日天'
# tbjx.change()
# print(name) # 日天
# print(tbjx.name) # barry
为模块起别名 **
1 简单,便捷.
# import hfjksdahdsafkd as sm
# print(sm.name)
2,有利于代码的简化.
# 原始写法
# result = input('请输入')
# if result == 'mysql':
# import mysql1
# mysql1.mysql()
# elif result == 'oracle':
# import oracle1
# oracle1.oracle()
# list.index()
# str.index()
# tuple.index()
# 起别名
# result = input('请输入')
# if result == 'mysql':
# import mysql1 as sm
# elif result == 'oracle':
# import oracle1 as sm
# ''' 后面还有很多'''
# sm.db() # 统一接口,归一化思想
导入多个模块
import time, os, sys # 这样写不好
# 应该向以下这种写法:
import time
import os
import sys
from ... import ...
from ... import ...的使用
# from tbjx import name
# from tbjx import read1
# from tbjx import read2
# print(name)
# print(globals())
# read1()
from ... import ... 与import对比 ***
1. from.. import 用起来更方便
from tbjx import name
print(name)
2. from...import 容易与本文件的名字产生冲突.
# 1, 容易产生冲突,后者将前者覆盖
# name = 'alex'
# from tbjx import name
# print(name)
3. 当前位置直接使用read1和read2,执行时,仍然以tbjx.py文件全局名称空间 ***
# from tbjx import read1
#
# def read1():
# print(666)
# name = '大壮'
# read1()
# print(globals())
from tbjx import change
name = 'Alex'
print(name) # 'Alex'
change() # 'barry'
from tbjx import name
print(name)
一行导入多个
from tbjx import name,read1,read2 # 这样不好
from tbjx import name
from tbjx import read1
from ... import *
模块循环导入的问题
py文件的两种功能py文件的两个功能:
1. 自己用 脚本
2. 被别人引用 模块使用
# print('from the tbjx.py')
__all__ = ['name', 'read1'] # 配合*使用
name = '太白金星'
def read1():
print('tbjx模块:', name)
def read2():
print('tbjx模块')
read1()
def change():
global name
name = 'barry'
print(name)
# print(__name__)
# 当tbjx.py做脚本: __name__ == __main__ 返回True
# 当tbjx.py做模块被别人引用时: __name__ == tbjx
# __name__ 根据文件的扮演的角色(脚本,模块)不同而得到不同的结果
#1, 模块需要调试时,加上 if __name__ == '__main__':
# import time
# change() # 测试代码
# if __name__ == '__main__':
# change()
# 2, 作为项目的启动文件需要用
模块的搜索路径
# import sm
import abc
# python 解释器会自动将一些内置内容(内置函数,内置模块等等)加载到内存中
import sys
# print(sys.modules) # 内置内容(内置函数,内置模块等等)
import time
# print(sys.path)
#['D:\\python_22\\day17', 'C:\\Python\\Python36\\python36.zip',
'C:\\Python\\Python36\\DLLs', 'C:\\Python\\Python36\\lib',
'C:\\Python\\Python36', 'C:\\Python\\Python36\\lib\\site-packages']# 'D:\\python_22\\day17' 路径是当前执行文件的相对路径
# import tbjx
# 我就想找到dz 内存没有,内置中,这两个你左右不了,sys.path你可以操作.
import sys
sys.path.append(r'D:\python_22\day16')
# sys.path 会自动将你的 当前目录的路径加载到列表中.
import dz
# 如果你想要引用你自定义的模块:
# 要不你就将这个模块放到当前目录下面,要不你就手动添加到sys.path
import sm
1. 它会先从内存中寻找有没有已经存在的以sm命名的名称空间.
2. 它会从内置的模块中找. time,sys,os,等等.
3. 他从sys.path中寻找.