一、hashlib模块
python中的hashlib模块提供了多种算法,常见的有md5,sha1等
什么是摘要算法呢?摘要算法又称哈希算法、散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。
摘要算法就是通过摘要函数f()对任意长度的数据data计算出固定长度的摘要digest,目的是为了发现原始数据是否被人篡改过。
摘要算法之所以能指出数据是否被篡改过,就是因为摘要函数是一个单向函数,计算f(data)很容易,但通过digest反推data却非常困难。而且,对原始数据做一个bit的修改,都会导致计算出的摘要完全不同。
我们以常见的摘要算法MD5为例,计算出一个字符串的MD5值:
import hashlib md5_obj = hashlib.md5() md5_obj.update('luffy0505'.encode('utf-8')) ret = md5_obj.hexdigest() print(ret, type(ret), len(ret)) # 字符串,32位 >>> 6073bad5650f16704ff978c1db5b921f <class 'str'> 32
摘要算法sha1:
import hashlib sha_obj = hashlib.sha1() sha_obj.update('luffy0505'.encode('utf-8')) ret = sha_obj.hexdigest() print(ret, type(ret), len(ret)) # 字符串 40位 >>> 4fb4ec3f87e3ab0f97a23cf0840dd029da5d88a0 <class 'str'> 40
hashlib算法
- 不可逆
- 不同的字符串通过这个算法的计算得到的密文总是不同的
- 相同的算法,相同的字符串,获得的结果总是相同的(即便是不同的语言,不同的环境(操作系统,版本,时间))
如果数据量很大,可以分块多次调用update(),最后计算的结果是一样的:

import hashlib md5_obj = hashlib.md5() md5_obj.update('hellof world'.encode('utf-8')) ret = md5_obj.hexdigest() print(ret) # 摘要值 153729f9fba1abc5aa39c8bae78a172e md5_obj1 = hashlib.md5() md5_obj1.update('hellof '.encode('utf-8')) md5_obj1.update('world'.encode('utf-8')) ret = md5_obj1.hexdigest() print(ret) # 153729f9fba1abc5aa39c8bae78a172e
总结如下:
hashlib 摘要算法,不是加密算法(不能解密) 多种算法 md5:32位16进制的数字字符组成的字符串 应用最广大的摘要算法 效率高,相对不复杂,如果只是传统摘要不安全。 sha算法:40位16进制的数字字符组成的字符串 sha算法要比md5算法更复杂 且shan n的数字越大算法越复杂,耗时越久,结果越长,越安全
hashlib应用:登录验证
登录验证 - hashlib 两种算法 md5,sha 常规验证 - 撞库 加盐 - 固定的盐 会导致恶意注册的用户密码泄露 动态加盐 - 每个用户都有一个固定的并且互不相同的盐 文件的一致性校验 给一个文件中的所有内容进行摘要算法得到一个md5的结果

def get_md5(s): md5_obj = hashlib.md5() md5_obj.update(s.encode('utf-8')) ret = md5_obj.hexdigest return ret username = input('username:') password = input('password:') with open('userinfo') as f: for line in f: usr, pwd = line.strip().split('|') if username == usr and get_md5(password) == pwd: print('登录成功') break else:print('登录失败')

# 加盐 # def get_md5(s): # md5_obj = hashlib.md5('盐'.encode('utf-8')) # md5_obj.update(s.encode('utf-8')) # ret = md5_obj.hexdigest # return ret # 动态加盐 # 每一个用户创建一个盐 - # def get_md5(user, s): # md5_obj = hashlib.md5(user.encode('utf-8')) # md5_obj.update(s.encode('utf-8')) # ret = md5_obj.hexdigest # return ret

import os,hashlib # def get_file_md5(file_path,buffer=1024): # md5_obj = hashlib.md5() # # file_path = r'3.习题讲解2.mp4' # 路径里不能有空格 # file_size = os.path.getsize(file_path) # with open(file_path, 'rb') as f: # while file_size: # content = f.read(buffer) # file_size -= len(content) # md5_obj.update(content) # return md5_obj.hexdigest() # # print(get_file_md5(r'3.习题讲解2.mp4'))
二、configparser模块
配置文件 'userinfo' 当你把你的程序copy到其他机器上其他目录下 如果你不是在当前这个userinfo文件所在的目录去执行py文件 绝对路径: 当你把整个程序包括userinfo文件copy到另一台机器上 开发环境 测试环境 生产环境 把路径记录在文件里

# 注释1 ; 注释2 [section1] k1 = v1 k2:v2 user=egon age=18 is_admin=true salary=31 [section2] k1 = v1

import configparser config=configparser.ConfigParser() config.read('a.cfg') #查看所有的标题 res=config.sections() #['section1', 'section2'] print(res) #查看标题section1下所有key=value的key options=config.options('section1') print(options) #['k1', 'k2', 'user', 'age', 'is_admin', 'salary'] #查看标题section1下所有key=value的(key,value)格式 item_list=config.items('section1') print(item_list) #[('k1', 'v1'), ('k2', 'v2'), ('user', 'egon'), ('age', '18'), ('is_admin', 'true'), ('salary', '31')] #查看标题section1下user的值=>字符串格式 val=config.get('section1','user') print(val) #egon #查看标题section1下age的值=>整数格式 val1=config.getint('section1','age') print(val1) #18 #查看标题section1下is_admin的值=>布尔值格式 val2=config.getboolean('section1','is_admin') print(val2) #True #查看标题section1下salary的值=>浮点型格式 val3=config.getfloat('section1','salary') print(val3) #31.0

import configparser config=configparser.ConfigParser() config.read('a.cfg',encoding='utf-8') #删除整个标题section2 config.remove_section('section2') #删除标题section1下的某个k1和k2 config.remove_option('section1','k1') config.remove_option('section1','k2') #判断是否存在某个标题 print(config.has_section('section1')) #判断标题section1下是否有user print(config.has_option('section1','')) #添加一个标题 config.add_section('egon') #在标题egon下添加name=egon,age=18的配置 config.set('egon','name','egon') config.set('egon','age',18) #报错,必须是字符串 #最后将修改的内容写入文件,完成最终的修改 config.write(open('a.cfg','w'))
三、logging模块
函数式简单配置
import logging logging.basicConfig(level=logging.INFO) logging.debug('debug message') # 计算或者工作的细节 logging.info('info message') # 记录一些用户的增删改查的操作 logging.warning('warning message') # 警告操作 logging.error('error message') # 错误操作 logging.critical('critical message') # 严重的错误 直接导致程序出错退出的操作
默认情况下Python的logging模块将日志打印到了标准输出中,且只显示了大于等于WARNING级别的日志,这说明默认的日志级别设置为WARNING(日志级别等级CRITICAL > ERROR > WARNING > INFO > DEBUG),默认的日志格式为日志级别:Logger名称:用户输出消息。
灵活配置,格式输出,输出位置
# logging.basicConfig(level=logging.INFO, # format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s', # datefmt='%c', # filename='test.log') # logging.warning('input a string type') # 警告操作 # logging.error('EOF ERROR ') # 警告操作 # logging.info('小明买了三条鱼') # 警告操作

logging.basicConfig()函数中可通过具体参数来更改logging模块默认行为,可用参数有: filename:用指定的文件名创建FiledHandler,这样日志会被存储在指定的文件中。 filemode:文件打开方式,在指定了filename时使用这个参数,默认值为“a”还可指定为“w”。 format:指定handler使用的日志显示格式。 datefmt:指定日期时间格式。 level:设置rootlogger(后边会讲解具体概念)的日志级别 stream:用指定的stream创建StreamHandler。可以指定输出到sys.stderr,sys.stdout或者文件(f=open(‘test.log’,’w’)),默认为sys.stderr。若同时列出了filename和stream两个参数,则stream参数会被忽略。 format参数中可能用到的格式化串: %(name)s Logger的名字 %(levelno)s 数字形式的日志级别 %(levelname)s 文本形式的日志级别 %(pathname)s 调用日志输出函数的模块的完整路径名,可能没有 %(filename)s 调用日志输出函数的模块的文件名 %(module)s 调用日志输出函数的模块名 %(funcName)s 调用日志输出函数的函数名 %(lineno)d 调用日志输出函数的语句所在的代码行 %(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示 %(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数 %(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒 %(thread)d 线程ID。可能没有 %(threadName)s 线程名。可能没有 %(process)d 进程ID。可能没有 %(message)s用户输出的消息
对象配置:解决中文问题。解决同时向文件和屏幕输出的问题。
# 先创建一个log对象 logger logger = logging.getLogger() logger.setLevel(logging.DEBUG) # 还要创建一个控制文件输出的文件操作符 fh = logging.FileHandler('mylog.log') # 还要创建一个控制屏幕输出的屏幕操作符 sh = logging.StreamHandler() # 要创建一个格式 fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') fmt2 = logging.Formatter('%(asctime)s - %(name)s[line:%(lineno)d] - %(levelname)s - %(message)s') # 文件操作符 绑定一个 格式 fh.setFormatter(fmt) # 屏幕操作符 绑定一个 格式 sh.setFormatter(fmt2) sh.setLevel(logging.WARNING) # logger对象来绑定:文件操作符, 屏幕操作符 logger.addHandler(sh) logger.addHandler(fh)
四、正则表达式与re模块
1.什么是正则:与字符串打交道的
正则就是用一些具有特殊含义的符号组合到一起(称为正则表达式)来描述字符或者字符串的方法。或者说:正则就是用来描述一类事物的规则。(在Python中)它内嵌在Python中,并通过 re 模块实现。正则表达式模式被编译成一系列的字节码,然后由用 C 编写的匹配引擎执行。
2.正则表达式的常见应用场景
a.判断某一个字符串是否符合规则:注册页
b.将符合规则的内容从一个庞大的字符串体系中提取出来:爬虫、日志分析
3.字符组
在字符组中所有的字符都可以匹配任意一个字符位置上出现的内容。如果在字符串中有任意的一个字符是字符组中的内容,那么就是匹配的项。
元字符 d 表示匹配一个数字[0-9] w 匹配数字,字母, 下划线 s 匹配任意空白键 匹配换行符 匹配制表符 匹配一个单词的边界 o hello world hello结尾的o h 开头 ^ 匹配字符串的开头 $ 匹配字符串结尾 ^hello$ hello hello hello 什么都匹配不到 hello 匹配到hello W 匹配非字母或数字下划线 D S a|b 匹配字符a或b () 匹配括号内的表达式,也表示一个组 [...] 匹配一个字符组 [^...] 匹配非字符组 . 匹配除了换行符之外的任意字符 a. 匹配a后一个字符 量词 * 重复零次或多次 可以0次 '+' + '?' ? + * + 重复一次或多次 最少1次 ?重复零次或一次 1次或0次 {n} 重复n次 {n,} 重复n次或多次 {n,m} 重复n到m次 d+ 整数 d+.d+ 小数 d+.d+|d+ 整数或小数 d+(.d+)? 整数或小数 分组 贪婪匹配:正则会尽量多的帮我们匹配
<.*> 默认贪婪 回溯算法
非贪婪匹配:会尽量少的为我们匹配
<.*?> 量词? 表示非贪婪 惰性匹配
.*?x 表示匹配任意长度任意字符遇到一个x就立即停止
元字符
元字符 量词
元字符 量词 ? 在量词的范围内尽量少的匹配这个元字符
分组 对某些固定的内容坐量词约束
或 把长的放在前面
转义符
pattern = r'\n'
s = r' '

身份证号码是一个长度为15或18个字符的字符串, 如果是15位则全部 数字组成,首位不能为0; [1-9]d{14} 如果是18位,首位不能为0 前17位全部是数字,末位可能是数字或x [1-9]d{16}[dx] [1-9]d{16}[dx]|[1-9]d{14} [1-9]d{14}(d{2}[dx])?
4.re模块
import re #findall ret = re.findall('d+', '334df32tjgjf3f') print(ret) # 参数 正则表达式 待匹配的字符串 # 返回值是一个列表,里面是所有匹配到的项 # search ret = re.search('d+', '334df32tjgjf3f') print(ret) if ret: print(ret.group()) # search 参数 正则表达式 待匹配的字符串 # 返回值: # 返回一个SRE_Match对象 # 通过group取值 # 且只包含第一个匹配到的值
# match
# match 从头开始匹配。相当于search('^d','3423jdfjsdl')
# 开头没有匹配上就返回None
ret = re.match('d+', '34483djsjsf085jd8dfsj')
print(ret)
print(ret.group())
# 替换sub
s = 'luffy12zoro34sanji'
ret = re.sub('d+', '|', s, 1) # 只替换一个
print(ret)
ret = re.subn('d+', '|', s, 2) #
print(ret) # 返回替换次数
# compile 编译正则规则
'''
正则-- > re模块 翻译 -- 字符串操作-- 编译-- 字节码-- 解释器执行代码
'''
com = re.compile('d+') # 代替正则规则
print(com)
ret = com.search('sdjgl3439dsfj')
print(ret.group())
ret = com.finditer('sg3j9sdgj0')
for i in ret:
print(i.group())
# finditer 迭代器 节省空间的方法
ret = re.finditer('d+', 'sjfsl32423jdfsd3d9')
print(ret)
for i in ret:
print(i.group())
findall优先级
# findall有一个特点,会优先显示分组中的内容 ret = re.findall('www.(baidu|bilibili).com','www.baidu.com') print(ret) # ['baidu'] 直接显示分组中的内容 ret2 = re.search('www.(baidu|bilibili).(com)','www.baidu.com') print(ret2.group(0)) # 默认 www.baidu.com print(ret2.group(1)) # 分组第一个元素 baidu print(ret2.group(2)) # 分组第二个元素 com
split优先级
# 切割split s = 'luffy123zoro456sanji' ret = re.split('d+', s) # 不保留数字 print(ret) ret2 = re.split('(d+)', s) # 保留数字 print(ret2) ret3 = re.split('d(d)', s) # 保留数字的倒数第二位 print(ret3) ret = re.split('(d)d', s) # 保留数字第一位 print(ret) #在匹配部分加上()之后所切出的结果是不同的, #没有()的没有保留所匹配的项,但是有()的却能够保留了匹配的项, #这个在某些需要保留匹配部分的使用过程是非常重要的
分组命名、分组约束
(?:正则表达式) 表示取消优先显示功能
(?P<组名>正则表达式) 表示给这个组起一个名字
(?P=组名) 表示引用之前组的名字,引用部分匹配到的内容必须和之前那个组中的内容一模一样
分组命名、分组约束 <h1>函数</h1> <a>函数</a> <(.*?)>.*?</(.*?)> pattern = '<(?P<tag>.*?)>.*?</(?P=tag)>' ret = re.search(pattern,'<h1>函数</h1>') print(ret) if ret: print(ret.group()) print(ret.group(1)) print(ret.group('tag')) pattern = r'<(.*?)>.*?</1>' ret = re.search(pattern,'<a>函数</a>') print(ret) if ret: print(ret.group()) print(ret.group(1))