序列化模块、加密模块
一. 昨日内容回顾
time datetime
1. time.time()
2. time.strftime("%Y-%m-%d %H:%M:%S")
3. time.localtime()
datetime
日志:logging
低配
标配
高配
# 高配版日志 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 = 'all2.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': { # 打印到终端的日志 # 屏幕句柄 'screen': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', # 打印到屏幕 'formatter': 'simple' }, # 打印到文件的日志,收集info及以上的日志 # 这里可以添加其他文件句柄,比如 "staff",后面记得也要修改 'file': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', # 保存到文件 'formatter': 'standard', 'filename': logfile_path, # 日志文件 'maxBytes': 1024*1024*5, # 日志大小 5M 'backupCount': 5, 'encoding': 'utf-8', # 日志文件的编码,再也不用担心中文log乱码了 }, }, 'loggers': { #logging.getLogger(__name__)拿到的logger配置 '': { 'handlers': ['screen', 'file'], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕 'level': 'DEBUG', # 总级别,一定要设置最低的,即debug 'propagate': True, # 向上(更高level的logger)传递 }, }, } def load_my_logging_cfg(): logging.config.dictConfig(LOGGING_DIC) # 导入上面定义的logging配置 # logger = logging.getLogger(__name__) # 生成一个log实例 logger = logging.getLogger("转账业务") logger.info('It works!') # 记录该文件的运行状态 if __name__ == '__main__': load_my_logging_cfg()
# 上面的程序要再加一个文件日志,只需在以下几步添加或修改: # 第一步: # logfile_name = 'all2.log' # log文件名 # # 第二步: # 'file': { # 'level': 'DEBUG', # 'class': 'logging.handlers.RotatingFileHandler', # 保存到文件 # 'formatter': 'standard', # # 第三步: # 'filename': logfile_path, # 日志文件 # 'maxBytes': 1024*1024*5, # 日志大小 5M # 'backupCount': 5, # 'encoding': 'utf-8', # 日志文件的编码,再也不用担心中文log乱码了 # }, # }, # 第四步: # 列表里对应修改。比如在第二步加了 "staff",这里要添加 "staff" # 'handlers': ['stream', 'file'], # 第五步 # logger = logging.getLogger("转账业务") # 这里的参数可按需求随便改 # logger.info('It works!') # 这里不仅参数可以随便改,logger.info也可以改为 logger.warning 之类的 # 可以有两个 LOGGING_DIC, 然后两个 def load_my_logging_cfg() 当然函数名要不一样,相应那五步也要改过来
def func(): """ 这是文档说明 :return: """ print(__name__) print(func.__name__) # func print(func.__doc__) # 这是文档说明 # :return: def func(): """ 这是文档说明 :return: """ print(func.__name__) func() # func
# 序列化模块(重要) # 网络数据传输只能通过 bytes 类型 # 文件写入内容(注意不是存储)即可以是 bytes, 也可以是 str dic = {"name": "太白金星", "hobby": ["戒烟", "烫不了头", "戒酒"]} s1 = str(dic) b1 = s1.encode("utf-8") print(b1) # b"{'name': '\xe5\xa4\xaa\xe7\x99\xbd\xe9\x87\x91\xe6\x98\x9f', 'hobby': ['\xe6\x88\x92\xe7\x83\x9f', '\xe7\x83\xab\xe4\xb8\x8d\xe4\xba\x86\xe5\xa4\xb4', '\xe6\x88\x92\xe9\x85\x92']}" s2 = b1.decode("utf-8") # print(s2, type(s2)) dic2 = eval(s2) print(dic2, type(dic2)) # {'name': '太白金星', 'hobby': ['戒烟', '烫不了头', '戒酒']} <class 'dict'>
# 但这是网络传输,使用 eval() 很危险,容易被黑客截取并替换成病毒文件 # 因此需要一个功能,能将数据转化成可以通过网络传输的 bytes # 序列化过程——将数据(数据结构,非字符串) 转化成 特殊的字符串(可以用于网络传输) # 反序列化过程——另外还得将这个特殊的字符串 转化成 原来的数据结构 # Python中的序列化模块有三种: # import json # json 序列化模块是所有语言通用的一种标准,也是一种数据转化格式 # 也就是说 Python 中的字典,可以通过 json 模块转化成 bytes 来进行网络传输成 java 的字典 # str, int, bool, dict, list(tuple), None 没有集合 # import pickle # pickle 也是序列化模块,但是只支持Python语言中所有数据类型(包括对象)的网络传输(用的少) # 写入文件时,可以写入多个 # import shelve # 了解 # shelve 序列化模块,只支持Python,一般与文件相关,即通过它把数据写入文件,拿出来后再反解成原数据类型
# json——安全模式的网络传输 # 项目中一般使用 json 来写 # 第一对方法:dumps loads # import json dic = {"name": "太白金星", "hobby": ["戒烟", "烫不了头", "戒酒"]} # json的序列化过程 s = json.dumps(dic) print(s, type(s)) # # {"name": "\u592a\u767d\u91d1\u661f", "hobby": ["\u6212\u70df", "\u70eb\u4e0d\u4e86\u5934", "\u6212\u9152"]} <class 'str'> s = json.dumps(dic, ensure_ascii=False) print(s) # {"name": "太白金星", "hobby": ["戒烟", "烫不了头", "戒酒"]} # json的反序列化过程 dic1 = json.loads(s) print(dic1, type(dic1)) # {'name': '太白金星', 'hobby': ['戒烟', '烫不了头', '戒酒']} <class 'dict'> # 注意 dic = {"name": "太白金星", "hobby": ["戒烟", "烫不了头", "戒酒"]} print(dic) # {'name': '太白金星', 'hobby': ['戒烟', '烫不了头', '戒酒']} # 字符串都是单引号,不过写的是双引号还是单引号 # 而上面的json.dumps 用法结果是双引号
# 第二对 dump load 与文件相关 dic = {"name": "太白金星", "hobby": ["戒烟", "烫不了头", "戒酒"]} with open("序列化.json", encoding="utf-8", mode="w") as f: # json.dump(dic, f) json.dump(dic, f, ensure_ascii=False) with open("序列化.json", encoding="utf-8") as f2: ret = json.load(f2) print(ret, type(ret)) # {'name': '太白金星', 'hobby': ['戒烟', '烫不了头', '戒酒']} <class 'dict'>
# 参数讲解 # ensure_ascii=False 显示中文 # sort_keys 按键的首字母的 ascii 排序 import json dic = {"name": "太白金星", "hobby": ["戒烟", "烫不了头", "戒酒"], "age": 18, "money": "一个亿"} s = json.dumps(dic, ensure_ascii=False, sort_keys=True) print(s) # # {"age": 18, "hobby": ["戒烟", "烫不了头", "戒酒"], "money": "一个亿", "name": "太白金星"}
# json 与 bytes 的区别 # bytes 只能操作str,用于网络传输 # json 可以操作 str, int, bool, dict, list(tuple), None 没有集合,用于网络传输,写入文件 # 通过json将多个字典写入一个文件 # 如果用 dump load 一个文件只能写入一个数据结构!!! import json dic1 = {"name": "abc"} dic2 = {"name": "def"} dic3 = {"name": "xyz"} # 下面这样不行 with open("多个字典.json", encoding="utf-8", mode="w") as f: json.dump(dic1, f) json.dump(dic2, f) json.dump(dic3, f) with open("多个字典.json", encoding="utf-8") as f1: ret1 = json.load(f1) ret2 = json.load(f1) ret3 = json.load(f1)
import json dic1 = {"name": "abc"} dic2 = {"name": "def"} dic3 = {"name": "xyz"} with open("多个字典.json", encoding="utf-8", mode="w") as f: f.write(json.dumps(dic1) + "\n") f.write(json.dumps(dic2) + "\n") f.write(json.dumps(dic3) + "\n") # 上面有结果,但是会飘红,不影响后面的操作 with open("多个字典.json", encoding="utf-8") as f1: for line in f1: print(json.loads(line)) # {"name": "abc"} # {"name": "def"} # {"name": "xyz"} # 总结 # dumps loads 用于网络传输和多个数据写入文件 # dump load 只能用于一个数据结构写入文件
# 一个小细节 import json dic = {1: "alex"} t = json.dumps(dic) print(t) # {"1": "alex"} # 发现数字1变成字符串形式了 print(json.loads(t)) # {'1': 'alex'} # 还原过后已经没有整数型了 # 算是一个 bug,一个坑
# pickle import pickle # dums, loads 用于网络传输,把所有数据类型转化成 bytes dic = {"name": "太白金星", "hobby": ["戒烟", "烫不了头", "戒酒"], "age": 18, "money": "一个亿"} s1 = pickle.dumps(dic) # print(s1) dic2 = pickle.loads(s1) print(dic2, type(dic2)) dic = {"name": "太白金星", "hobby": ["戒烟", "烫不了头", "戒酒"], "age": 18, "money": "一个亿"} with open("p1.pickle", mode="wb") as f: pickle.dump(dic, f) with open("p1.pickle", mode="rb") as f1: ret = pickle.load(f1) print(ret, type(ret)) # {'name': '太白金星', 'hobby': ['戒烟', '烫不了头', '戒酒'], 'age': 18, 'money': '一个亿'} <class 'dict'>
# 利用 dump 和 load 将多个数据写入文件,json的不行 dic1 = {"name": "abc"} dic2 = {"name": "def"} dic3 = {"name": "xyz"} # 注意这里不用写明 encoding,因为带 b 的不能用!!! with open("p2.pickle", mode="wb") as f: pickle.dump(dic1, f) pickle.dump(dic2, f) pickle.dump(dic3, f) with open("p2.pickle", mode="rb") as f1: ret1 = pickle.load(f1) ret2 = pickle.load(f1) ret3 = pickle.load(f1) print(ret1, ret2, ret3) # {'name': 'abc'} {'name': 'def'} {'name': 'xyz'} def func(): print(666) with open("p3.pickle", mode="wb") as f: pickle.dump(func, f) with open("p3.pickle", mode="rb") as f1: ret = pickle.load(f1) ret() # 666
# shelve # helve也是python提供给我们的序列化工具,比pickle用起来更简单一些。 # shelve只提供给我们一个open方法,是用key来访问的,使用起来和字典类似。 import shelve f = shelve.open('shelve_file') f['key'] = {'int':10, 'float':9.5, 'string':'Sample data'} #直接对文件句柄操作,就可以存入数据 f.close() # 上面相当于给文件写入一个字典,{"key": {'int':10, 'float':9.5, 'string':'Sample data'}} # 结果有三个文件 f = shelve.open('shelve_file') existing = f1['key'] #取出数据的时候也只需要直接用key获取即可,但是如果key不存在会报错 f1.close() print(existing)
# 这个模块有个限制,它不支持多个应用同一时间往同一个DB进行写操作。 # 所以当应用只进行读操作,可以让shelve通过只读方式打开DB import shelve f = shelve.open('shelve_file', flag='r') existing = f['key'] f.close() print(existing) # 由于shelve在默认情况下是不会记录待持久化对象的任何修改的 # 所以在shelve.open()时候需要修改默认参数,否则对象的修改不会保存。 import shelve f1 = shelve.open('shelve_file') print(f1['key']) f1['key']['new_value'] = 'this was not here before' f1.close() # 如果想对 shelve 文件进行修改,必须要加这个参数 f2 = shelve.open('shelve_file', writeback=True) print(f2['key']) f2['key']['new_value'] = 'this was not here before' f2.close()
# 加密模块 摘要算法 一堆加密算法的集合体 # hshlib的规则——将 str 通过算法得到一串等长度的 数字 # 1.不同的字符串转化成的数字肯定不同 # 2.相同的字符串即使在不同的计算机,只要使用相同的加密方式,转化成的数字一定相同 # 3.hashlib 加密不可逆,不能破解 # 给密码加密 import hashlib # md5 ret = hashlib.md5() ret.update("好123".encode("utf-8")) print(ret.hexdigest()) # 51cb75f82eceb17b86f019e01618d75e ret = hashlib.md5() ret.update("asjdlkajlkajsdlkajljlajsalsj".encode("utf-8")) print(ret.hexdigest()) # 77e2bcc002a6a2a957519303d16a976a # 可以看出,不管字符串多长,它都是转化成一样长的数字 # 加盐——让密码更复杂 ret = hashlib.md5("老男孩教育".encode("utf-8")) ret.update("123456".encode("utf-8")) print(ret.hexdigest()) # b08404ec951d75b6da37fdd1bfb8c1e9 # 动态盐 username = input("请输入用户名: ").strip() # ret = hashlib.md5(username.encode("utf-8")) ret = hashlib.md5(username[::2].encode("utf-8")) ret.update("123456".encode("utf-8")) print(ret.hexdigest()) # asdak # 331c5caa07f18d43b7c6302202bd4d2d
# md5 加密效率快,通用,安全性相对差 # sha 系列,算法更好,安全性高,效率低,耗时长 # sha系列 ret = hashlib.sha512() ret.update("aksdjalksjdalkj".encode("utf-8")) print(ret.hexdigest()) # 093bc658d3b1a40ba2f4b41a8f86cca8cb79f7808e52 # 75e69303988afd938722f97d5ac4565f2a494d75dabcdbca6 # 1e446fd7c4a14c29bfa711a7a34fa84d1e2 # 加盐 ret = hashlib.sha512("好好学习".encode("utf-8")) ret.update("aksdjalksjdalkj".encode("utf-8")) print(ret.hexdigest()) # a17ae4ede113d8969df70b5a6547b41164dfcd56423d # 4d2a761f625558f22b4791f4808132db994cbc9ec76ccdb # 6b3a44d17b71e043fbcbc5584f96f9a38b88d # 动态盐 username = input("请输入用户名:").strip() ret = hashlib.sha512(username.encode("utf-8")) ret.update("aksdjalksjdalkj".encode("utf-8")) print(ret.hexdigest()) # askdj # ee97bc4ad328e098fef08395c07309adf7746e37dec81d # 2e123d34fa81ffceaaa9c15f4cb2ba8544c5a9217640e0e # 48953f12dd0ac820176a3982260ad70fc8f
# 文件校验 def check_md5(file): ret = hashlib.md5() with open(file, encoding="utf-8", mode="rb") as f: ret.update(f.read()) return ret.hexdigest() print(check_md5("文件校验1")) print(check_md5("文件校验2")) # 注意一定要有两个文件,内容一样 # "文件校验1与当前文件在同一个文件夹 # 这里有个问题,如果文件太大占内存,因此用 for循环hao ret = hashlib.md5() ret.update("好好学习并且天天向上".encode("utf-8")) print(ret.hexdigest()) ret.update("好好学习".encode("utf-8")) ret.update("并且".encode("utf-8")) ret.update("天天向上".encode("utf-8")) print(ret.hexdigest()) # 上面两个结果一样
# 大文件校验 # 因此,文件较大时,可以这样写 # 按照一行一行读取 def check_md5(file): ret = hashlib.md5() with open(file, mode="rb") as f: for line in f: ret.update(line) return ret.hexdigest() print(check_md5("test")) print(check_md5("test01")) # 8a9a779e5bacbd752fb5deb05581f86e # 8a9a779e5bacbd752fb5deb05581f86e # 按照字节读取 def check_md5(file): ret = hashlib.md5() with open(file, mode="rb") as f: while 1: content = f.read(1024) # 这个表示每次最多读取这么多字节,而不是每次读取1024个字节 if content: # 如果真表示一直能取到内容 ret.update(content) else: break # 因此这里文件的最后内容不够1024个字节时,把剩下的读取出来,然后退出 return ret.hexdigest() print(check_md5("test")) print(check_md5("test01")) # 8a9a779e5bacbd752fb5deb05581f86e # 8a9a779e5bacbd752fb5deb05581f86e
os sys 模块
os模块是与操作系统交互的一个接口
工作目录,父级目录,当前目录都是此文件从属的文件夹路径
绝对路径——从根目录到当前文件的文件名
相对路径——同一个文件夹下的文件的相对关系
当前执行这个python文件的工作目录相关的工作路径
获取当前工作目录,即当前python脚本工作的目录路径
os.getcwd()
改变当前脚本工作目录;相当于shell下cd
os.chdir("dirname")
返回当前目录: ('.')
os.curdir
获取当前目录的父目录字符串名:('..')
os.pardir
和文件夹相关
可生成多层递归目录
os.makedirs('dirname1/dirname2')
若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推
os.removedirs('dirname1')
生成单级目录;相当于shell中mkdir dirname
os.mkdir('dirname')
删除单级空目录,若目录不为空则无法删除,报错;相当于shell中rmdir
dirname
os.rmdir('dirname')
列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印
os.listdir('dirname')
和文件相关
删除一个文件
os.remove()
重命名文件/目录
os.rename("oldname","newname")
获取文件/目录信息
os.stat('path/filename')
path系列,和路径相关
返回path规范化的绝对路径
import os
os.path.abspath(path)
print(os.path.abspath("模块.py")) # 获取绝对路径
G:\ATM\模块.py
将path分割成目录和文件名二元组返回
os.path.split(path)
返回path的目录。其实就是os.path.split(path)的第一个元素
os.path.dirname(path)
返回path最后的文件名。如何path以/或\结尾,那么就会返回空值,
即os.path.split(path)的第二个元素。
os.path.basename(path)
如果path存在,返回True;如果path不存在,返回False
os.path.exists(path)
如果path是绝对路径,返回True
os.path.isabs(path)
如果path是一个存在的文件,返回True。否则返回False
os.path.isfile(path)
如果path是一个存在的目录,则返回True。否则返回False
os.path.isdir(path)
将多个路径组合后返回,第一个绝对路径之前的参数将被忽略
os.path.join(path1[, path2[, ...]])
返回path所指向的文件或者目录的最后访问时间
os.path.getatime(path)
返回path所指向的文件或者目录的最后修改时间
os.path.getmtime(path)
返回path的大小
os.path.getsize(path)
sys模块是与Python解释器交互的一个接口
sys.argv 命令行参数List,第一个元素是程序本身路径
sys.exit(n) 退出程序,正常退出时exit(0),错误退出sys.exit(1)
sys.version 获取Python解释程序的版本信息
sys.path 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值
sys.platform 返回操作系统平台名称
import sys
try:
sys.exit(1)
except SystemExit as e:
print(e)
collections模块
在内置数据类型(dict、list、set、tuple)的基础上
collections模块还提供了几个额外的数据类型:
Counter、deque、defaultdict、namedtuple和OrderedDict等。
# 1.namedtuple: 生成可以使用名字来访问元素内容的tuple # 命名元组 # tuple可以表示不变集合,例如,一个点的二维坐标就可以表示成: # p = (1, 2) # 看到(1, 2),很难看出这个tuple是用来表示一个坐标的,所以应该这样: from collections import namedtuple Point = namedtuple('Point', ['x', 'y']) p = Point(1, 2) print(p) # Point(x=1, y=2) print(p[0]) # 1 print(p.x) # 1 print(p.x + p.y) # 3 # 类似的,如果要用坐标和半径表示一个圆,也可以用namedtuple定义: # namedtuple('名称', [属性list]): Circle = namedtuple('Circle', ['x', 'y', 'r'])
# 2.deque: 双向队列,可以快速的从另外一侧追加和推出对象,也是数据类型 # 比如购物时只有前10人有优惠。 # 使用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']) # pop()也一样
# 3.defaultdict: 带有默认值的字典 # 有如下值集合 [11,22,33,44,55,66,77,88,99,90...] # 将所有大于 66 的值保存至字典的第一个key中,将小于 66 的值保存至第二个key的值中。 li = [11,22,33,44,55,77,88,99,90] # 原生字典解决办法 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) # defaultdict字典解决办法 from collections import defaultdict values = [11, 22, 33,44,55,66,77,88,99,90] my_dict = defaultdict(list) # print(my_dict["key1"]) # [] # print(my_dict["key2"]) # [] for value in values: if value > 66: my_dict["key1"].append(value) else: my_dict["key2"].append(value) print(my_dict) # defaultdict(<class 'list'>, # {'key2': [11, 22, 33, 44, 55, 66], 'key1': [77, 88, 99, 90]}) # 构建一个字典,字典的key 从1-100,对应的值都是666 # 第一种方法 dic = {} for i in range(1, 101): dic[i] = 666 print(dic) # 第二种方法 dic = dict.fromkeys(range(1, 101), 666) print(dic) # 第三种方法 # 字典推导式 print({key:666 for key in range(1, 101)}) # 第四种方法 from collections import defaultdict def func(): return 666 my_dict = defaultdict(func) for i in range(1, 101): my_dict[i] print(my_dict)
# 4.Counter: 计数器,主要用来计数 # Counter类的目的是用来跟踪值出现的次数。 # 它是一个无序的容器类型,以字典的键值对形式存储 # 其中元素作为key,其计数作为value。 # 计数值可以是任意的Interger(包括0和负数)。 # Counter类和其他语言的bags或multisets很相似。 from collections import Counter c = Counter('abcdeabcdabcaba') print(c) # Counter({'a': 5, 'b': 4, 'c': 3, 'd': 2, 'e': 1})