常用模块(二)

一、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))

  

原文地址:https://www.cnblogs.com/eaoo/p/9600062.html