序列化模块、加密模块

. 昨日内容回顾
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模块
在内置数据类型(dictlistsettuple)的基础上
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})

 

posted @ 2019-01-09 14:50  星满夜空  阅读(433)  评论(0编辑  收藏  举报