3.3-模块


模块

内置模块

序列化模块

什么是序列化模块:本质是将一种数据结构(如字典,列表)等转化成一个特殊的序列(字符串或者bytes)的过程就叫做序列化。

dic = {'name': '黑色利穆'}
ret = str(dic)
print(ret,type(ret))
# 特殊的序列,并不是这种str字符串;

为什么要序列化模块

比如:程序中需要一个字典类型的数据存放个人信息

dic = {'username': '黑色利穆', 'password': 123, 'login_status': True}

程序中有一些地方需要使用这个dic数据,登陆时会使用,注册时也会使用。如果提前将这个dic放在全局,不合理。应该将数据写入一个地方存储(没学数据库)。放在文件里。name程序需要这个数据的时候,就读取这个文件,拿出需要的数据即可。但是字典直接写入文件是不行的,必须转化成字符串的形式,而且读取出来的也是字符串形式的字典;

拿到str(dic)就没用,因为转化不成dic(不能用eval很危险),所以很不方便。这时候序列化模块就起到作用了。如果写入文件中的字符串是一个序列化后的特殊的字符串,那么当从文件中读取出来,是可以转化回元数据结构的。

json序列化,pickle序列化:

序列化模块就是将一个常见的数据结构转化成一个特殊的序列,并且这个特殊的序列还可以反解回去。主要用途:文件读写数据,网络传输数据;

python中这种序列化模块有三种:

json模块:(重点)

  1. 不同语言都遵循的一种数据转化格式,即不同语言都使用的特殊字符串。(比如python的一个列表[1,2,3]利用json转化成特殊的字符串,然后在编码成bytes发送给php开发者,php开发者可以解码成特殊的字符串,然后再反解成原数据组(列表):[1,2,3];
  2. json序列化只支持部分python数据结构:dict,list,tuple,str,int,float,True,False,None;

pickle模块:

  1. 只能是python语言遵循的一种数据转化格式,只能在python语言中使用;
  2. 支持python所有的数据类型包括实例化对象;

shelve模块:类似于字典的操作方式去操作特殊的字符串;

序列化模块中使用的最多的就是json模块;

json模块

json模块是将满足条件的数据结构转化成特殊的字符串,并且也可以反序列化还原回去;

序列化模块总共只有两种用法:用于网络传输的中间环节,用于文件存储的中间环节:

用于网络传输:dumps,loads

用于文件读写:dump,load

dumps,loads

import json
dic = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3', 'k4': 'v4'}
str_dic = json.dumps(dic)       # 序列化:将一个字典转化成一个字符串
print(type(str_dic), str_dic)   # <class 'str'> {"k1": "v1", "k2": "v2", "k3": "v3", "k4": "v4"}
# json转化完的字符串类型的字典中的字符串是由""表示的;

dic2 = json.loads(str_dic)      # 反序列化:将一个字符串格式的字典转化成一个字典
# 要用json的loads功能处理的字符串类型的字典中的字符串必须由""表示;
print(type(dic2), dic2)         # <class 'dict'> {'k1': 'v1', 'k2': 'v2', 'k3': 'v3', 'k4': 'v4'}



list_dic = [1, ['a', 'b', 'c'], 2, {'k1': 'v1', 'k2': 'v2'}]
str_dic = json.dumps(list_dic)      # 也可以处理嵌套的数据类型
print(type(str_dic), str_dic)       # <class 'str'> [1, ["a", "b", "c"], 2, {"k1": "v1", "k2": "v2"}]

list_dic2 = json.loads(str_dic)
print(type(list_dic2), list_dic2)   # <class 'list'> [1, ['a', 'b', 'c'], 2, {'k1': 'v1', 'k2': 'v2'}]

dump,load

import json
f = open('json_file.json', 'w')
dic = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
json.dump(dic, f)       # dump方法接收一个文件句柄,直接将字典转换成json字符串写入文件
f.close()


# json文件也是文件,就是专门存储json字符串的文件
f = open('json_file.json')
dic2 = json.load(f)     # load方法接收一个文件句柄,直接将文件中的json字符串转化成数据结构返回
f.close()
print(type(dic2), dic2)
# <class 'dict'> {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}

其他参数

ensure_ascii 当它为True的时候,所有非ASCII码字符显示为\uXXXX序列,只需在dump时将ensure_ascii设置为False即可,此时存入json的中文即可正常显示。

separators 分隔符,实际上是(item_separator, dict_separator) 的一个元组,默认的就是(',' , ':');这表示dictionary内keys之间用','隔开,而key和value之间用':'隔开。

sort_keys 将数据根据keys的值进行排序;

import json
data = {'username': ['zhangsan', 'lisi'], 'sex': 'male', 'age': 17}
json_dic2 = json.dumps(data, sort_keys=True, indent=4, separators=(',', ':'), ensure_ascii=False)
print(json_dic2)

# {
#     "age":17,
#     "sex":"male",
#     "username":[
#         "zhangsan",
#         "lisi"
#     ]
# }

json序列化存储多个数据到同一个文件中

对于json序列化,存储多个数据到一个文件中是有问题的。默认一个json文件只能存储一个json数据,但是也可以解决:

import json
# 对于json存储多个数据到文件中
dic1 = {'name': '黑色利穆1'}
dic2 = {'name': '黑色利穆2'}
dic3 = {'name': '黑色利穆3'}
dic4 = {'name': '黑色利穆4'}
f = open('序列化', encoding='utf-8', mode='a')
json.dump(dic1, f)
json.dump(dic2, f)
json.dump(dic3, f)
json.dump(dic4, f)
f.close()

f = open('序列化', encoding='utf-8')
ret1 = json.load(f)
ret2 = json.load(f)
ret3 = json.load(f)
ret4 = json.load(f)
print(ret1)
# 报错

# 解决方式:
import json
# 对于json存储多个数据到文件中
dic1 = {'name': '黑色利穆1'}
dic2 = {'name': '黑色利穆2'}
dic3 = {'name': '黑色利穆3'}
dic4 = {'name': '黑色利穆4'}
f = open('序列化', encoding='utf-8', mode='a')
str1 = json.dumps(dic1)
f.write(str1 + '\n')
str2 = json.dumps(dic2)
f.write(str2 + '\n')
str3 = json.dumps(dic3)
f.write(str3 + '\n')
str4 = json.dumps(dic4)
f.write(str4 + '\n')
f.close()


f = open('序列化', encoding='utf-8')
for line in f:
    print(json.loads(line))

pickle模块

pickle模块是将python所有的数据结构以及对象等转化成bytes类型,然后还可以反序列化还原回去;

pickle模块只能python语言识别的序列化模块。如果把序列化模块比如成全世界公认的一种交流语言,json就像是英语,全世界(python,java,php,c)都遵循这个标准。而pickle就是中文,只有python作为第一交流语言;

用于网络传输:dumps,loads

用于文件读写:dump,load

dumps, loads

import pickle
dic = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
str_dic = pickle.dumps(dic)
print(type(str_dic), str_dic)
# bytes类型

dic2 = pickle.loads(str_dic)
print(type(dic2), dic2)
# 字典


# 序列化对象
def func():
    print(777)
ret = pickle.dumps(func)
print(ret, type(ret))       # b'\x80\x03c__main__\nfunc\nq\x00.' <class 'bytes'>

f1 = pickle.loads(ret)      # f1得到func函数的内存地址
f1()        # 执行func函数

dump,load

import pickle
dic = {(1, 2):'黑色利穆', 1:True, 'set':{1,2,3}}
f = open('pickle序列化', mode='wb')
pickle.dump(dic, f)
f.close()

with open('pickle序列化', mode='wb') as f1:
    pickle.dump(dic, f1)

with open('pickle序列化', mode='rb') as f:
    dic1 = pickle.load(f)
print(dic1)

pickle序列化存储多个数据到一个文件中

import pickle
dic1 = {'name': '黑色利穆1'}
dic2 = {'name': '黑色利穆2'}
dic3 = {'name': '黑色利穆3'}
f = open('pick多数据', mode='wb')
pickle.dump(dic1, f)
pickle.dump(dic2, f)
pickle.dump(dic3, f)
f.close()

f = open('pick多数据', mode='rb')
while True:
    try:
        print(pickle.load(f))
    except EOFError:
        break
f.close()

如果序列化的内容是列表或者字典,推荐使用json模块。

如果是其他的数据类型,且未来会用python进行反序列化,使用pickle

打印进度条

'''
#=========知识储备==========
#进度条的效果
[#             ]
[##            ]
[###           ]
[####          ]
'''
# 指定宽度
print('[%-15s]' %'#')
print('[%-15s]' %'##')
print('[%-15s]' %'###')
print('[%-15s]' %'####')

# 打印%
print('%s%%' %(100))        # 第二个%表示取消第一个%的特殊意义

# 通过传参控制宽度
print('[%%-%ds]' %50)           # [%-50s]
print(('[%%-%ds]' %50) %'#')
print(('[%%-%ds]' %50) %'##')
print(('[%%-%ds]' %50) %'###')
print(('[%%-%ds]' %50) %'####')

# 实现打印进度条
import sys
import os
import time
def progress(percent, width=50):
    if percent >= 1:
        percent=1
    show_str = ('%%-%ds' % width) % (int(width*percent)*'-')
    print('\r%s %d%%' %(show_str, int(100*percent)), end='')

# 应用
data_size = 100000
recv_size = 0
while recv_size < data_size:
    time.sleep(0.1)         # 模拟网络的传输延迟
    recv_size += 1024       # 每次收10240

    percent = recv_size/data_size       # 接收的比例
    progress(percent, width=70)         # 进度条的宽度70

time模块

和时间有关的要用到时间模块

import time
time.sleep(2)		# 推迟指定的时间运行
print(time.time())	# 获取当前时间戳

表示时间的方式:

  1. 时间戳:时间戳表示从1970年1月1日00:00:00开始按秒计算的偏移量;
  2. 格式化时间字符串:
  3. 元组:struct_time元组共有9个元素(年,月, 日,时,分,秒,一年中第几周,一年中第几天)
# 格式化时间字符串:
%y	两位数的年份表示
%Y	四位数的年份表示
%m	月份
%d	月内的一天
%H	24小时制小时数
%I	12小时制小时数
%M	分钟数
%S	秒
%a	本地简化星期名称
%A	本地完整星期名称
%b	本地简化月份名称
%B	本地完整月份名称
%c	本地相应的日期表示和时间表示
%j	年内的一天
%p	本地A.M. 或P.M. 的等价符
%U	一年中的星期数,星期天为星期的开始
%w	星期,星期天为星期的开始
%W	一年中的星期数,星期一为星期的开始
%x	本地相应的日期表示
%X	本地相应的时间表示
%Z	当前时区的名称

元组表示:
索引(Index)	属性(Attribute)		值(Values)
0				tm_year(年)			比如2011
1				tm_mon(月)			1-12
2				tm_mday(日)			1-31
3				tm_hour(时)			0-23
4				tm_min(分)			0-59
5				tm_sec(秒)			0-60
6				tm_wday(weekday)	  0-6(0表示周一)
7				tm_yday(一年中第几天)  1-366
8				tm_isdst(是否夏令时)	  默认为0

import time
# 时间戳
print(time.time())
# 1606986125.560002


# 时间字符串
print(time.strftime('%Y-%m-%d %X'))
# 2020-12-03 17:02:42
print(time.strftime('%Y-%m-%d %H-%M-%S'))
# 2020-12-03 17-03-10


# 时间元组:localtime将一个时间戳转化为当前时区的struct_time
print(time.localtime())
# time.struct_time(tm_year=2020, tm_mon=12, tm_mday=3, tm_hour=17, tm_min=4, tm_sec=5, tm_wday=3, tm_yday=338, tm_isdst=0)

# 时间戳是计算机能够识别的时间;时间字符串是人能看懂的时间;元组是用来操作时间的

几种格式之间的转换

struct_time -- Timestamp mktime

Timestamp -- struct_time localtime gmtime

struct_time -- Format string strftime

Format string -- struct_time strptime

import time
# 格式化时间   --- 》 结构化时间
ft = time.strftime('%Y/%m/%d %H:%M:%S')
st = time.strptime(ft, '%Y/%m/%d %H:%M:%S')
print(ft)
print(st)
# 2020/12/03 17:13:25
# time.struct_time(tm_year=2020, tm_mon=12, tm_mday=3, tm_hour=17, tm_min=13, tm_sec=25, tm_wday=3, tm_yday=338, tm_isdst=-1)



# 结构化时间  -- 》 时间戳
t = time.mktime(st)
print(t)
# 1606986846.0



# 时间戳  -- 》 结构化时间
t = time.time()
st = time.localtime(t)
print(st)
# time.struct_time(tm_year=2020, tm_mon=12, tm_mday=3, tm_hour=17, tm_min=21, tm_sec=23, tm_wday=3, tm_yday=338, tm_isdst=0)


# 结构化时间   --- 》  格式化时间
ft = time.strftime('%Y/%m/%d %H:%M:%S', st)
print(ft)
# 2020/12/03 17:22:08

import time
# 结构化时间  -- 》 %a %b %d %H:%M:%S %Y串
# time.asctime(结构化时间),如果不传参数,直接返回当前时间的格式化串
print(time.asctime(time.localtime(1500000000)))
# Fri Jul 14 10:40:00 2017
print(time.asctime())
# Thu Dec  3 17:24:38 2020



# 时间戳  --- 》 %a %b %d %H:%M:%S %Y串
# time.ctime(时间戳)   如果不传参,直接返回当前时间的格式化串
print(time.ctime())
# Thu Dec  3 17:25:56 2020
print(time.ctime(15000000))
# Tue Jun 23 22:40:00 1970


t = time.time()
ft = time.ctime(t)
print(ft)
# Thu Dec  3 17:26:45 2020

st = time.localtime()
ft = time.asctime(st)
print(ft)
# Thu Dec  3 17:27:04 2020

计算时间差

import time
true_time = time.mktime(time.strptime('2000-10-10 10:10:10', '%Y-%m-%d %H:%M:%S'))
time_now = time.mktime(time.strptime('2020-11-11 11:11:10', '%Y-%m-%d %H:%M:%S'))
dif_time = time_now - true_time
struct_time = time.gmtime(dif_time)
print('过去了%d年%d月%d天%d小时%d分钟%d秒'%(struct_time.tm_year-1970, struct_time.tm_mon-1,
                                 struct_time.tm_mday-1, struct_time.tm_hour,
                                 struct_time.tm_mday, struct_time.tm_sec))

datetime模块

import datetime
now_time = datetime.datetime.now()          # 现在时间
# 只能调整的字段:weeks,days,hours,minutes,seconds
print(datetime.datetime.now() + datetime.timedelta(weeks=3))        # 三周后
print(datetime.datetime.now() + datetime.timedelta(weeks=-3))       # 三周前

print(datetime.datetime.now() + datetime.timedelta(days=3))         # 三天后
print(datetime.datetime.now() + datetime.timedelta(days=-3))        # 三天前

print(datetime.datetime.now() + datetime.timedelta(hours=3))        # 三小时后
print(datetime.datetime.now() + datetime.timedelta(hours=-3))       # 三小时前

print(datetime.datetime.now() + datetime.timedelta(minutes=3))      # 三分钟后
print(datetime.datetime.now() + datetime.timedelta(minutes=-3))     # 三分钟前

print(datetime.datetime.now() + datetime.timedelta(seconds=3))      # 三秒后
print(datetime.datetime.now() + datetime.timedelta(seconds=-3))     # 三秒前


current_time = datetime.datetime.now()
# 调整到指定的年月日时分秒
print(current_time.replace(year=2000))          # 调整到2000年
print(current_time.replace(month=1))            # 调整到1月
print(current_time.replace(year=1996, month=11, day=8))     # 1996-11-08 17:41:30.653111


# 将时间戳转化成时间
print(datetime.date.fromtimestamp(15000000000))     # 2445-05-01

random模块

import random
# 随机小数
print(random.random())          # 大于0 小于1 之间的小数
print(random.uniform(1, 10))    # 大于1 小于10 之间的小数


# 随机整数
print(random.randint(1, 11))    # 大于等于1 小于等于11 之间的整数
print(random.randrange(1, 10, 2))   # 大于等于1 小于 10之间的奇数


# 随机选择一个返回
print(random.choice([1, '2', [3, 4, 5]]))       # 1 或2 或[3,4,5]
# 随机选择多个返回,返回的个数为函数的第二个参数
print(random.sample([1, '2', [3, 4, 5]], 2))    # 列表元素任何2个组合


# 打乱列表顺序
item = [1, 3, 5, 7, 9]
random.shuffle(item)        # 打乱次序
print(item)     # [7, 5, 1, 3, 9]

生成随机验证码

# 生成随机验证码
import random
def v_code():
    code = ''
    for i in range(5):
        num = random.randint(0, 9)
        alf = chr(random.randint(65, 90))
        add = random.choice([num, alf])
        code = "".join([code, str(add)])
    return code
print(v_code())

os模块

os模块是与操作系统交互的一个接口,提供的功能多与工作目录,路径,文件相关。

目录指:文件夹,当前目录,工作目录,父级目录;

import os
# 当前执行这个python文件的工作目录相关的工作路径
os.getcwd()     # 获取当前工作目录,即当前python脚本工作的目录路径
os.chdir("dirname")     # 改变当前脚本工作目录,相当于shell下的cd
os.curdir       # 返回当前目录 ('.')
os.pardir         # 获取当前目录的父级目录字符串名 ('..')


# 和文件夹相关
os.makedirs('dirname1/dirname2')        # 生成多层递归目录
os.removedirs('dirname1/dirname2')       # 若目录为空,则删除,并递归到上一级目录。如若也空,则删除,以此类推
os.mkdir('dirname')     # 生成单级目录
os.rmdir('dirname')     # 删除单级目录,若目录不为空则无法删除
os.listdir('dirname')     # 列出指定目录下的所有文件和子目录,包括隐藏文件,以列表方式打印


# 和文件相关
os.remove()     # 删除一个文件
os.rename("oldname", "newname")     # 重命名文件/目录
os.stat('path/filename')        # 获取文件/目录信息


# 和操作系统差异相关
os.sep      # 输出操作系统特定的路径分隔符,win下为'\\', linux下为'/'
os.linesep  # 输出当前平台使用的行终止符,win下为'\t\n', linux下为'\n'
os.pathsep  # 输出用于分割文件路径的字符串,win下为';', linux下为':'
os.name     # 输出字符串指示当前使用平台,win下为'nt', linux下为'posix'


# 和执行系统命令相关
os.system('bash command')       # 运行shell命令
os.popen("bash command).read()")        # 运行shell命令,获取执行结果
os.environ      # 获取系统环境变量


# path系列,和路径相关
os.path.abspath(path)       # 返回path规范化的绝对路径
os.path.split(path)       # 将path分割成目录和文件名 二元组返回
os.path.dirname(path)     # 返回path目录,其实就是os.path.split(path)的第一个元素
os.path.basename(path)    # 返回path最后的文件名,如果path以/或\结尾,就返回空值
os.path.exists(path)        # 如果path存在,返回true;如果path不存在,返回false
os.path.isabs(path)         # 如果path是绝对路径,返回True
os.path.isfile(path)        # 如果path是存在的文件,返回True
os.path.isdir(path)         # 如果path是一个目录,返回True
os.path.join(path1[, path2[, ..]])      # 将多个路径组合后返回,第一个绝对路径之前的参数被忽略
os.path.getatime(path)      # 返回path所指向的文件或目录的最后访问时间;
os.path.getmtime(path)      # 返回path所指向的文件或目录的最后修改时间;
os.path.getsize(path)       # 返回path的大小

os.stat('path/filename') # 获取文件/目录信息 结构说明

stat结构:
st_mode		inode保护模式
st_ino		inode节点号
st_dev		inode驻留的设备
st_nlink	inode链接数
st_uid		所有者的用户id
st_gid		所有者的组id
st_size		普通文件以字节为单位的大小;包含等待某些特殊文件的数据
st_atime	上次访问时间
st_mtime	最后一次修改时间
st_ctime	操作系统报告的ctime。unix是最新的元数据更改时间,windows是创建时间

sys模块

sys模块是与python解释器交互的一个借口

sys.argv			命令行参数list,第一个元素时程序本身路径
sys.exit(n)			退出程序,正常退出exit(0), 错误退出sys.exit(1)
sys.version			获取python解释程序的版本信息
sys.path			返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值
sys.platform		返回操作系统平台名称

# 异常处理和status
import sys
try:
    sys.exit(1)
except SystemError as e:
    print(e)

hashlib模块

用途:加密和校验使用。

原理:通过一个函数,把任意长度的数据按照一定规则转为一个固定长度的数据传(通常是16进制的字符串表示);

hashlib模块的特征以及使用要点:

  1. bytes类型数据 --- 通过hashlib算法 ---- 固定长度的字符串
  2. 不同的bytes类型数据转化成的结果一定不同;
  3. 相同的bytes类型数据转化成的结果与一定相同;
  4. 此转化过程不可逆;

密码的加密

普通加密

常见的摘要算法MD5:

import hashlib
md5 = hashlib.md5()
md5.update('123456'.encode('utf-8'))
print(md5.hexdigest())
# 计算结果如下下:
# e10adc3949ba59abbe56e057f20f883e

# 相同的bytes数据转化的结果一定相同;
# 不同的bytes数据转化的结果一定不同;

这种加密级别是最低的,相对来说不够安全。虽然hashlib加密是不可逆的加密方式,但是也是可以破解的。常用的破解方式是以空间换时间,把一些常用的密码以及md5值做成对应关系,类似于字典;

dic = {'e10adc3949ba59abbe56e057f20f883e': 123456}

然后通过密文获取对应的密码,只要空间足够大,利用空间换取破解时间;

更安全的加密方式:加盐

加盐加密

什么叫加盐?这个名字来源于烧烤,俗称BBQ。烧烤时,一般在快熟的时候,会给肉串撒盐,增加味道。这个撒盐的工序外国人认为比较复杂,所以就讲比较复杂的加密方式称之为加盐;

固定的盐

import hashlib
ret = hashlib.md5('黑色利穆'.encode('utf-8'))		# '黑色利穆'就是固定的盐
ret.update('123456'.encode('utf-8'))
print(ret.hexdigest())
# d0f0af2af5ac0ea7815c0c8c3db992a7
# 在所有密码的前边增加一个固定的盐,这样提高了密码的安全性。如果固定的盐被窃取,还是可以破解出来的。所以,还可以加动态的盐

动态的盐

import hashlib
username = '黑色利穆'
ret = hashlib.md5(username[::2].encode('utf-8'))        # 针对每个账号,每个账号的盐都不一样
ret.update('123456'.encode('utf-8'))
print(ret.hexdigest())
# 1c0dc95b755740d64db39869b668ef36

这样安全性就大大提高了。

hashlib模块是一个算法集合,包含很多加密算法。md5只是其中一种,一般使用md5就够了。但是对于安全性比较高的行业,比如金融,md5加密的方式就不够了。需要加密方式更高的。比如sha系列,sha1,sha224,sha512等,数字越大,加密的方法越复杂,安全性越高,但是效率会越慢;

import hashlib
ret = hashlib.sha1()
ret.update('heiselimu'.encode('utf-8'))
print(ret.hexdigest())
# aeeaa21c7303a623a6f1fabb220e9dca11880bab

# 可以加盐
ret = hashlib.sha384(b'heise')
ret.update('heiseilimu'.encode('utf-8'))
print(ret.hexdigest())
# 5cca9563e5898439dbbd0ca64a621c9c756b298cc3d080eb1104bf0b06424a5770713197c73ebc2cc53948317c8413b7

# 加动态的盐
ret = hashlib.sha384(b'heise'[::2])
ret.update('heiselimu'.encode('utf-8'))
print(ret.hexdigest())
# aa857a1bdc03194c3b6018791bc964fc88867cd2b63162bb11f3dd37db5ee64e39bba04e8231555d1015cebc83cc549e

文件的一致性校验

hashlib模块除了可以用于密码加密之外,还有一个常用的功能,就是文件的一致性校验;

md5计算的是bytes类型的数据的转换值,同一个bytes数据用同样的加密方式转化成的结果一定相同。如果不同的bytes数据(即使是数据只删除了一个空格)那么用同样的加密方式转化的结果一定是不同的。所以,shalib也是验证文件一致性的重要工具;

low版文件校验

import hashlib
def func(file):
    with open(file, mode='rb') as f:
        ret = hashlib.md5()
        ret.update(f.read())
        return ret.hexdigest()
print(func('hashlib_file1'))
# f254feb1738da867cd53f979e9dff699
# 这样就可以计算文件的md5值,从而进行文件校验。
# 如果文件过大,全部读取就会撑爆内存,所以要分段读取;

分段读取

import hashlib
# 直接update
md5obj = hashlib.md5()
md5obj.update('黑色利穆 ,世界上最帅的 男人'.encode('utf-8'))
print(md5obj.hexdigest())
# 8f02c4364852768f9ab680950e8486cb

# 分段update
md5obj = hashlib.md5()
md5obj.update('黑色利穆'.encode('utf-8'))
md5obj.update(' ,世界上'.encode('utf-8'))
md5obj.update('最帅的 '.encode('utf-8'))
md5obj.update('男人'.encode('utf-8'))
print(md5obj.hexdigest())
# 8f02c4364852768f9ab680950e8486cb

高大上版文件校验

根据上面的分段代码

校验版本文件的sha256值是否相同;

import hashlib
def file_check(file_path):
    with open(file_path, mode='rb') as f:
        sha256 = hashlib.sha256()
        while 1:
            content = f.read(1024)
            if content:
                sha256.update(content)
            else:
                return sha256.hexdigest()

print(file_check('app-release_137_jiagu_sign.apk.1'))
# a0e72364d9cc64d33a5e60b89532e560a7af608d363d639ebadc51054b61e243

collections模块

在内置数据类型(dict,list,set,tuple)的基础上,collections模块还提供了几个额外的数据类型:Counter,deque,defaultdict,namedtuple,OrderedDict

  1. namedtuple 生成可以使用名字来访问元素内容的tuple
  2. deque 双端列队,可以快速从另一侧追加和推出对象
  3. Counter 计数器,主要用来计数
  4. OrderedDict 有序字典
  5. defaultdict 带有默认值的字典

namedtuple

tuple可以表示不变集合。例如,一个点的二维坐标可以表示成 p = (1, 2)

但是看到(1,2),很难看出这个tuple是用来表示坐标的,这时可以使用namedtuple

from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(1, 2)
print(p.x, p.y)     # 1 2

类似的,如果用坐标和半径表示一个圆:可以用namedtuple定义

from collections import namedtuple
# namedtuple('名称', [属性list]):
Circle = namedtuple('Circle', ['x', 'y', 'r'])

deque

使用list存储数据时,按索引访问元素很快,但是插入和删除元素就很慢。因为list是线性存储,数据量大的时候,插入和删除效率很低。

deque是为了高效实现插入和删除操作的双向列表,适用于队列和栈;

from collections import deque
q = deque(['a', 'b', 'c'])
q.append('x')
q.appendleft('y')
print(q)
# deque(['y', 'a', 'b', 'c', 'x'])
# deque除了实现list的append()和pop()之外,还支持appendleft()和popleft(),可以高效地往头部添加或删除元素;

OrderedDict

使用dict时,key是无序的,对dict做迭代时,无法确定key的顺序。如果要保持key的顺序,可以用OrderedDict

from collections import OrderedDict
d = dict([('a', 1), ('b', 2), ('c', 3)])
print(d)    # dict的key是无序的
# {'a': 1, 'b': 2, 'c': 3}
od = OrderedDict(d)
print(od)   # OrderedDict的key是有序的
# OrderedDict([('a', 1), ('b', 2), ('c', 3)])

ps:OrderedDict的key会按照插入的顺序排列,而不是key本身排序;

from collections import OrderedDict
od = OrderedDict()
od['z'] = 1
od['y'] = 2
od['x'] = 3
print(od.keys())        # 按照插入的key的顺序返回
# odict_keys(['z', 'y', 'x'])

defaultdict

有如下集合[11,22,33,44,55,66,77,88,99] 将所有大于66的值保存至字典的第一个key中,小于66的值保存在第二个key中

即:{’k1': 大于66, 'k2': 小于66}

li = [11,22,33,44,55,66,77,88,99]
result = {}
for row in li:
    if row > 66:
        if 'key1' not in result:
            result['key1'] = []
        result['key1'].append(row)
    else:
        if 'key2' not in result:
            result['key2'] = []
        result['key2'].append(row)
print(result)
# {'key2': [11, 22, 33, 44, 55, 66], 'key1': [77, 88, 99]}


from collections import defaultdict
values = [11,22,33,44,55,66,77,88,99]
my_dict = defaultdict(list)
for value in values:
    if value > 66:
        my_dict['k1'].append(value)
    else:
        my_dict['k2'].append(value)
print(my_dict)
# defaultdict(<class 'list'>, {'k2': [11, 22, 33, 44, 55, 66], 'k1': [77, 88, 99]})

使用dict时,如果引用的key不存在,就会抛出KeyError,如果希望key不存在时,返回一个默认值,就可以用defaultdict

from collections import defaultdict
dd = defaultdict(lambda: 'key不存在')
dd['key1'] = 'abcd'
print(dd['key1'])
print(dd['key2'])
# abcd
# key不存在

Counter

Counter类的目的是用来跟踪值出现的次数,是一个无序的容器类型,以字典的键值对形式存储,其中元素作为key,计数作为value。计数值可以是任意的Interger(包括0和负数)。

from collections import Counter
a = Counter('aaaaaaaaabbbbbbbbbbbbcvadsfsadfsdfa sdf asdf')
print(a)
# Counter({'a': 13, 'b': 12, 'd': 5, 's': 5, 'f': 5, ' ': 2, 'c': 1, 'v': 1})

logging模块

函数式简单配置

import logging
logging.debug('debug message')
logging.info('info message')
logging.warning('warning message')
logging.error('error message')
logging.critical('critical message')
# 默认情况下pythyon的logging模块将日志打印到了标准输出中,且只显示了大于等于WARNING级别的日志。说明默认的日志级别设置为WARNING。日志级别登记CRITICAL > ERROR > WARNING > INFO > DEBUG)。默认的日志格式为 日志级别: Logging名称: 用户输出信息;

# 灵活配置日志级别,日志格式,输出位置
import logging
logging.basicConfig(level=logging.DEBUG),
                    format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
                    datefmt='%a, %d %b %Y %H:%M:%S',
                    filename='/tmp/test.log',
                    filemode='w')

logging.debug('debug message')
logging.info('info message')
logging.warning('warning message')
logging.error('error message')
logging.critical('critical message')

# 详细解释
'''
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用户输出的消息
'''

logger对象配置

import logging
logger = logging.getLogger()

# 创建一个handler,用于写入日志文件
fh = logging.FileHandler('test.log', encoding='utf-8')

# 再创建一个handler,用于输出到控制台
ch = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setLevel(logging.DEBUG)

fh.setFormatter(formatter)
ch.setFormatter(formatter)
logger.addHandler(fh)       # logger对象可以添加多个fh和ch对象
logger.addHandler(ch)

logger.debug('logger debug message')
logger.info('logger info message')
logger.warning('logger warning message')
logger.error('logger error message')
logger.critical('logger cirical message')
# 2020-12-03 15:22:35,722 - root - WARNING - logger warning message
# 2020-12-03 15:22:35,723 - root - ERROR - logger error message
# 2020-12-03 15:22:35,723 - root - CRITICAL - logger cirical message

logging库提供了很多组件:Logger,Handler,Filter,Formatter。

Logger对象提供应用程序可直接使用的接口,Handler发送日志到适当的目的地,Filter提供了过滤日志信息的方法,Formatter指定日志显示格式。另外,可以通过logger.setLevel(logging.Debug)设置级别,也可以通过fh.setLevel(logging.Debug)单对文件流设置某个级别;

logger的配置文件

用logger对象配置日志功能,需要创建各种对象,比如logger对象,fileHandler对象,ScreamHandler对象等等。比较麻烦,可以用字典的方式创建logger配置文件,可以做到拿来即用

import os
import logging.config

# 定义日志输出格式
standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \
                  '[%(levelname)s][%(message)s]'    # 其中name为getlogger指定的名字

simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'

id_simple_format = '[%(levelname)s][%(asctime)s] %(message)s'


logfile_dir = os.path.dirname(os.path.abspath(__file__))        # log文件的目录
logfile_name = 'access.log'     # log文件名

# 如果不存在定义的日志目录就创建一个
if not os.path.isdir(logfile_dir):
    os.mkdir(logfile_dir)

# log文件的全路径
logfile_path = os.path.join(logfile_dir, logfile_name)

# log配置字典
LOGGING_DIC = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'standard': {
            'format': standard_format
        },
        'simple': {
            'format': simple_format
        },
    },
    'filters': {},
    'handlers': {
        # 打印到终端的日志
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',   # 打印到屏幕
            'formatter': 'simple'
        },
        # 打印到文件的日志,收集info即以上的日志
        'default': {
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',    # 保存到文件
            'formatter': 'standard',
            'filename': logfile_path,   # 日志文件
            'maxBytes': 1024*1024*5,    # 日志大小5M
            'backupCount': 5,
            'encoding': 'utf-8',    # 日志文件的编码
        },
    },
    'loggers': {
        # logging.getLogger(__name__)拿到logger配置
        '': {
            'handlers': ['default', 'console'], # 把上边定义的两个handler都加上,即写文件又打印到屏幕
            'level': 'DEBUG',
            'propagate': True,      # 向上(更高level的logger)传递
        },
    },
}


def load_my_logging_cfg():
    logging.config.dictConfig(LOGGING_DIC)      # 导入上面定义的logging配置
    logger = logging.getLogger(__name__)        # 生成一个log实例
    logger.info('It works')         # 记录该文件的运行记录


if __name__ == '__main__':
    load_my_logging_cfg()

'''
1. 有了上述方式的好处:所有与logging模块有关的配置都写到字典中就可以了,更加清晰,方便管理;
2. 需要解决的问题:
    1. 从字典加载配置: logging.config.dictConfig(settings.LOGGING_DIC)
    2. 拿到logger对象来产生日志
        logger对象都是配置到字典的loggers键对应的子字典中的,
        按照对我们logging模块的理解,要想获取某个东西都是通过名字,就是key获取的。于是要获取到不同的logger对象就要
        logger = logging.getLogger('loggers子字典的key名')
        
        问题是:如果我们想要不同的logger名的logger对象都共用一段配置,那么肯定不能在loggers子字典中定义n个key
    'loggers': {
        # logging.getLogger(__name__)拿到logger配置
        'l1': {
            'handlers': ['default', 'console'], # 把上边定义的两个handler都加上,即写文件又打印到屏幕
            'level': 'DEBUG',
            'propagate': True,      # 向上(更高level的logger)传递
        },
        'l2': {
            'handlers': ['default', 'console'], # 把上边定义的两个handler都加上,即写文件又打印到屏幕
            'level': 'DEBUG',
            'propagate': True,      # 向上(更高level的logger)传递
        },
        'l3': {
            'handlers': ['default', 'console'], # 把上边定义的两个handler都加上,即写文件又打印到屏幕
            'level': 'DEBUG',
            'propagate': True,      # 向上(更高level的logger)传递
        },
    },

# 解决方式:定义一个空的key
    'loggers': {
        '': {
            'handlers': ['default', 'console'],
            'level': 'DEBUG',
            'propagate': True,
        },
    },
这样再去logger对象时
logging.getLogger(__name__), 不同的文件__name__不同,保证了打印日志时标识信息不同,但是拿着该名字去loggers里找key名时却找不到,于是默认用key=''的配置
'''

posted @ 2020-12-03 18:55  黑色利穆  阅读(85)  评论(0编辑  收藏  举报