【一】re
【二】time、datetime
【1】表示时间模块(time/datetime)的三种方式
- 在Python中,通常有这三种方式来表示时间:
- 时间戳
- 元组(struct_time)
- 格式化的时间字符串:
- 格式化的时间字符串(Format String): ‘1999-12-06’
【2】time
(1)导入时间模块
import time
(2)时间戳(time)
生成时间戳
import time
time_str = time.time()
时间戳转换为时间元组(UTC时间),与英国伦敦当地时间一致
import time
time_str = time.time()
print(time.gmtime(time_str))
#time.struct_time(tm_year=2023, tm_mon=12, tm_mday=17, tm_hour=7, tm_min=51, tm_sec=58, tm_wday=6, tm_yday=351, tm_isdst=0)
时间戳转换为时间元组(当地时间)
- 当地时间。例如我们现在在北京执行这个方法:与UTC时间相差8小时,UTC时间+8小时 = 北京时间
import time
time_str = time.time()
print(time.localtime(time_str))
# time.struct_time(tm_year=2023, tm_mon=12, tm_mday=17, tm_hour=15, tm_min=54, tm_sec=22, tm_wday=6, tm_yday=351, tm_isdst=0)
(3)时间字符串(strftime)
import time
time_now = time.strftime("%Y-%m-%d %X")
print(time_now)
# 2023-12-17 15:57:05
time_now_new = time.strftime("%Y-%m-%d %H-%M-%S")
print(time_now_new)
# 2023-12-17 15-58-00
- Python中时间日期格式化符号
符号 | 含义 | 值 |
---|---|---|
%y | 两位数的年份表示 | (00-99) |
%Y | 四位数的年份表示 | (000-9999) |
%m | 月份 | (01-12) |
%d | 月内中的一天 | (0-31) |
%H | 24小时制小时数 | (0-23) |
%I | 12小时制小时数 | (01-12) |
%M | 分钟数 | (00=59) |
%S | 秒 | (00-59) |
%a | 本地简化星期名称 | |
%A | 本地完整星期名称 | |
%b | 本地简化的月份名称 | |
%B | 本地完整的月份名称 | |
%c | 本地相应的日期表示和时间表示 | |
%j | 年内的一天 | (001-366) |
%p | 本地A.M.或P.M.的等价符 | |
%U | 一年中的星期数 | (00-53)星期天为星期的开始 |
%w | 星期 | (0-6),星期天为星期的开始 |
%W | 一年中的星期数 | (00-53)星期一为星期的开始 |
%x | 本地相应的日期表示 | |
%X | 本地相应的时间表示 | |
%Z | 当前时区的名称 | |
%% | %号本身 |
(4)时间元组(struct_time)
- localtime将一个时间戳转换为当前时区的struct_time
import time
data_time = time.localtime()
# 查看元组形式的时间
print(data_time)
# time.struct_time(tm_year=2023, tm_mon=12, tm_mday=17, tm_hour=16, tm_min=3, tm_sec=39, tm_wday=6, tm_yday=351, tm_isdst=0)
# 可以通过直接 '.'取每个值
print(data_time.tm_year)
# 索引取每个值
print(data_time[0])
# 2023
# 2023
- struct_time 元组共有9个元素共九个元素:
- (年,月,日,时,分,秒,一年中第几周,一年中第几天等)
索引(Index) | 属性(Attribute) | 值(Values) |
---|---|---|
0 | (年) | 比如2023 |
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 |
(5)小结
- 时间戳是计算机能够识别的时间;
- 时间字符串是人能够看懂的时间;
- 元组则是用来操作时间的
【3】datetime
(1)导出模块
import datetime
(2)自定义日期并格式化
import datetime
res = datetime.date(2023,12,17)
print(res)
# 2023-12-17
(3)获取本地时间
# 年月日时分秒
import datetime
now_time = datetime.datetime.today()
print(now_time)
# 2023-12-17 16:27:03.997333
- 无论是年月日,还是年月日时分秒对象
- 都可以调用以下方法获取针对性的数据
- 以datetime对象举例
import datetime
# 获取当日的日期
now_time = datetime.datetime.today()
print(now_time)
# 2023-12-17 16:32:05.805267
# 获取年份
print(now_time.year)
# 2023
# 获取月份
print(now_time.month)
# 12
# 获取日
print(now_time.day)
# 17
# 获取星期(weekday星期是0-6) 0表示周一
print(now_time.weekday())
# 4
# 获取星期(weekday星期是1-7) 1表示周一
print(now_time.isoweekday())
# 5
(4)timedelta对象
[1]打印时间增减
import datetime
# 操作时间 : 对时间对象加减
t_day = datetime.timedelta(days=7)
print(t_day)
# 7 days, 0:00:00
t_day = datetime.timedelta(days=6)
print(t_day)
# 6 days, 0:00:00
[2]推迟日期
import datetime
# 获取本地年月日
today = datetime.date.today()
print(today)
# 2023-11-24
# 打印今天的日期
print('今天的日期:{}'.format(today))
# 今天的日期:2023-11-24
# 打印七天后的日期
t_day = datetime.timedelta(days=7)
today = datetime.date.today()
print('从今天向后推7天:{}'.format(t_day + today))
# 从今天向后推7天:2023-12-01
# 打印七天前的日期
t_day = datetime.timedelta(days=7)
today = datetime.date.today()
print('从今天向前推7天:{}'.format(today - t_day))
# 从今天向前推7天:2023-11-17
[3]日期对象与timedelta之间的关系
- 日期对象 = 日期对象 +/- timedelta对象
- timedelta对象 = 日期对象 +/- 日期对象
import datetime
day_now = datetime.date.today()
day_to = datetime.timedelta(days=6)
print(day_now)
# 2023-11-24
print(day_to)
# 6 days, 0:00:00
# 日期对象 = 日期对象 +/- timedelta对象
now_date = day_now + day_to
print(now_date) # 2023-11-30
print(type(now_date)) # <class 'datetime.date'>
# timedelta对象 = 日期对象 +/- 日期对象
lta2 = day_now - now_date
print(lta2) # -6 days, 0:00:00
print(type(lta2)) # <class 'datetime.timedelta'>
- 计算生日
import datetime
birthday = datetime.date(2023, 11, 24)
now_date = datetime.date.today()
today = datetime.date.today()
days = birthday - now_date
print('生日:{}'.format(birthday))
print('今天的日期:{}'.format(today))
print('距离生日还有{}天'.format(days))
# 生日:2023-11-24
# 今天的日期:2023-11-24
# 距离生日还有0:00:00天
(5)小结
import datetime
dt_today = datetime.datetime.today()
dt_now = datetime.datetime.now()
dt_utcnow = datetime.datetime.utcnow() # UTC时间与我们的北京时间cha ju
print(dt_today)
# 2023-11-24 22:15:43.241507
print(dt_now)
# 2023-11-24 22:15:43.241506
print(dt_utcnow)
# 2023-11-24 14:15:43.241506
【三】os
__file__是指当前文件
【1】文件路径相关(path)
(1)获取当前文件路径(abspath)
import os
file_path = os.path.abspath(__file__)
print(file_path)
# E:\PycharmProjects\demo\use.py
(2)获取当前文件所在文件夹路径(dirname)
import os
file_path = os.path.dirname(__file__)
print(file_path)
# E:\PycharmProjects\demo
(3)判断当前路径是否存在(exists)
import os
file_path_one = "E:\PycharmProjects\demo"
is_true_one = os.path.exists(file_path_one)
print(is_true_one)
# True
import os
file_path_one = "E:\PycharmProjects\demo\sdsad"
is_true_one = os.path.exists(file_path_one)
print(is_true_one)
# Fales
(4)拼接文件路径(join)
import os
BASE_DIR = os.path.dirname(__file__)
print(BASE_DIR)
# E:\PycharmProjects\demo
file_name = 'img'
file_path = os.path.join(BASE_DIR,file_name)
print(file_path)
# E:\PycharmProjects\demo\img
(5)切割路径(split)
import os
BASE_DIR = os.path.abspath(__file__)
file_path_list = os.path.split(BASE_DIR)
print(file_path_list)
# ('E:\\PycharmProjects\\demo', 'use.py')
(6)获取结尾文件/文件夹名(basename)
import os
BASE_DIR = os.path.abspath(__file__)
file_path = os.path.join(BASE_DIR, 'img')
print(file_path)
# 返回path最后的文件名
# 如何 path以/或\结尾,那么就会返回空值。
# 即 os.path.split(path) 的第二个元素
is_true = os.path.basename(file_path)
print(is_true)
# img
file_path_one = os.path.abspath(__file__)
print(file_path_one)
# E:\PycharmProjects\demo\use.py
print(os.path.basename(file_path_one))
# use.py
(7)当前路径是否是文件(isfile)
import os
BASE_DIR = os.path.dirname(__file__)
print(BASE_DIR)
# E:\PycharmProjects\demo
file_path = os.path.abspath(__file__)
print(file_path)
# E:\PycharmProjects\demo\use.py
is_true_one = os.path.isfile(BASE_DIR)
print(is_true_one)
# False
is_true_two = os.path.isfile(file_path)
print(is_true_two)
# True
(8)当前路径是否为绝对路径(isabs)
import os
file_path_one = r'E:\PycharmProjects\demo\use.py'
file_path_two = r'../use.py'
is_true_one = os.path.isabs(file_path_one)
print(is_true_one)
# True
is_true_two = os.path.isabs(file_path_two)
print(is_true_two)
# False
(9)当前文件目录是否存在(isdir)
import os
file_path_one = r'E:\PycharmProjects\demo'
file_path_two = r'..'
is_true_one = os.path.isdir(file_path_one)
print(is_true_one)
# True
is_true_two = os.path.isdir(file_path_two)
print(is_true_two)
# True
(10)获取当前文件或目录的最后访问时间(getatime)
import os,time
BASE_DIR = os.path.dirname(__file__)
file_path = os.path.abspath(__file__)
file_base_time = os.path.getatime(BASE_DIR)
print(file_base_time) # 1702812410.646089
print(time.strftime("%Y-%m-%d %H-%M-%S", time.localtime(file_base_time)))
# 2023-12-17 19-26-50
file_time = os.path.getatime(file_path)
print(time.strftime("%Y-%m-%d %H-%M-%S", time.localtime(file_time)))
# 2023-12-17 19-26-50
(11)获取当前文件或者目录的创建时间(getctime)
import os,time
BASE_DIR = os.path.dirname(__file__)
file_path = os.path.abspath(__file__)
file_base_time = os.path.getatime(BASE_DIR)
print(file_base_time) # 1702812663.7310612
print(time.strftime("%Y-%m-%d %H-%M-%S", time.localtime(file_base_time)))
# 2023-12-17 19-31-03
file_time = os.path.getctime(file_path)
print(time.strftime("%Y-%m-%d %H-%M-%S", time.localtime(file_time)))
# 2023-12-17 10-26-08
(12)返回当前文件或路径的最后修改时间(getmtime)
import os, time
BASE_DIR = os.path.dirname(__file__)
file_path = os.path.abspath(__file__)
file_base_time = os.path.getmtime(BASE_DIR)
print(file_base_time) # 1702813628.777038
print(time.strftime("%Y-%m-%d %H-%M-%S", time.localtime(file_base_time)))
# 2023-12-17 19-47-08
file_time = os.path.getmtime(file_path)
print(time.strftime("%Y-%m-%d %H-%M-%S", time.localtime(file_time)))
# 2023-12-17 19-47-08
(13)返回当前文件的大小(getsize)
import os
BASE_DIR = os.path.dirname(__file__)
file_path = os.path.abspath(__file__)
file_base_time = os.path.getsize(BASE_DIR)
print(file_base_time) # 0
file_time = os.path.getsize(file_path)
print(file_time) # 4969
【3】路径操作(os)
(1)创建单级文件夹(mkdir)
只能创建单级路径,多级会报错
import os
BASE_DIR = os.path.dirname(__file__)
print(BASE_DIR)
file_name = 'img'
file_path = os.path.join(BASE_DIR, file_name)
print(file_path)
if not os.path.exists(file_path):
os.mkdir(file_path)
print(f"{file_path} :>>> 已创建")
print(f"当前路径 :>>>> {os.path.exists(file_path)}")
# E:\PycharmProjects\demo
# E:\PycharmProjects\demo\img
# E:\PycharmProjects\demo\img :>>> 已创建
# 当前路径 :>>>> True
(2)创建多级文件夹(makedirs)
【四】random
【1】导入模块
import random
【2】随机小数
(1)默认区间的小数(random)
- 大于0且小于1之间的小数
import random
res = random.random()
print(res)
(2)指定区间的小数(uniform)
import random
res = random.uniform(1,5)
print(res)
# 4.618207388285267
【3】随机整数
# 区间
import random
print(random.randint(1,20))
# 随机区间奇偶数
import random
print(random.randrange(1,20))
# 7
【4】随机返回
# 随机抽取一个元素
v = random.choice([11, 22, 33, 44, 55])
print(v)
# 随机抽取多个元素
v = random.sample([11, 22, 33, 44, 55], 3)
print(v)
【5】打乱列表顺序(shuffle)
data = [1, 2, 3, 4, 5, 6, 7, 8, 9]
random.shuffle(data)
print(data)
【五】json、pickle
【1】什么是序列化
- 将原本的字典、列表等内容转换成一个字符串的过程就叫做序列化。
【2】为什么要有序列化
-
比如,我们在
python
代码中计算的一个数据需要给另外一段程序使用,那我们怎么给?
- 现在我们能想到的方法就是存在文件里
- 然后另一个
python
程序再从文件里读出来。
-
但是我们都知道
- 对于文件来说是没有字典这个概念的
- 所以我们只能将数据转换成字典放到文件中。
-
你一定会问,将字典转换成一个字符串很简单,就是
str(dic)
就可以办到了
- 为什么我们还要学习序列化模块呢?
-
没错序列化的过程就是从
dic
变成str(dic)
的过程。 -
现在你可以通过
str(dic)
- 将一个名为
dic
的字典转换成一个字符串,
- 将一个名为
-
但是你要怎么把一个字符串转换成字典呢?
-
聪明的你肯定想到了
eval()
,如果我们将一个字符串类型的字典str_dic
传给eval
,就会得到一个返回的字典类型了。 -
eval()函数十分强大,但是
eval
是做什么的?
- 官方demo解释为:
- 将字符串
str
当成有效的表达式来求值并返回计算结果。 BUT!
强大的函数有代价。安全性是其最大的缺点。
-
-
想象一下
- 如果我们从文件中读出的不是一个数据结构,
- 而是一句"删除文件"类似的破坏性语句
- 那么后果实在不堪设设想。
而使用eval
就要担这个风险。
-
所以
- 我们并不推荐用
eval
方法来进行反序列化操作(将str
转换成python
中的数据结构)
- 我们并不推荐用
【3】序列化的目的
- 以某种存储形式使自定义对象持久化;
- 将对象从一个地方传递到另一个地方。
- 使程序更具维护性。
【4】序列化模块json
(1)序列化方法(loads)
import json
data_dict = '{"zhangsan":1234}'
data = json.loads(data_dict)
print(data)
(2)反序列化(dumps)
import json
data_dict = {"zhangsan":1234}
data = json.dumps(data_dict)
print(data)
(3)写入文件(dump)
import json
data_dict = {"zhangsan": 12345}
with open("data.json", 'w') as file:
json.dump(data_dict, file)
(4)读文件数据(load)
import json
data_dict = {"zhangsan": 12345}
with open("data.json", 'r') as file:
data = json.load(file)
print(data)
【5】序列化模块pickle
- json & pickle 模块 (用于序列化的两个模块)
- json
- 用于字符串 和 python数据类型间进行转换
- pickle
- 用于python特有的类型 和 python的数据类型间进行转换
(1)序列化方法(dumps)
import pickle
dic = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
str_dic = pickle.dumps(dic)
print(str_dic)
# b'\x80\x04\x95#\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x02k1\x94\x8c\x02v1\x94\x8c\x02k2\x94\x8c\x02v2\x94\x8c\x02k3\x94\x8c\x02v3\x94u.'
print(type(str_dic))
# <class 'bytes'>
(2)反序列化方法(loads)
import pickle
dic = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
str_dic = pickle.dumps(dic)
print(str_dic)
# b'\x80\x04\x95#\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x02k1\x94\x8c\x02v1\x94\x8c\x02k2\x94\x8c\x02v2\x94\x8c\x02k3\x94\x8c\x02v3\x94u.'
print(type(str_dic))
# <class 'bytes'>
dic2 = pickle.loads(str_dic)
print(dic2)
# {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
print(type(dic2))
# <class 'dict'>
(3)写入文件(dump)
import pickle
import time
struct_time = time.localtime()
print(struct_time)
# time.struct_time(tm_year=2023, tm_mon=11, tm_mday=25, tm_hour=20, tm_min=9, tm_sec=46, tm_wday=5, tm_yday=329, tm_isdst=0)
# 打开文件,并将句柄给 f
f = open('pickle_file', 'wb')
# 向 pickle_file 文件中写入数据
pickle.dump(struct_time, f)
# 完成写入,关闭文件
f.close()
(4)读文件数据(load)
import pickle
# 打开文件的句柄
f = open('pickle_file', 'rb')
# 将读出来的数据给 struct_time2
struct_time2 = pickle.load(f)
# 我们可以直接操作 struct_time2 --- 上面直接写入的时间元组对象
print(struct_time2)
# time.struct_time(tm_year=2023, tm_mon=11, tm_mday=25, tm_hour=20, tm_min=9, tm_sec=46, tm_wday=5, tm_yday=329, tm_isdst=0)
print(type(struct_time2))
# <class 'time.struct_time'>
print(struct_time2.tm_year)
# 2023
【6】小练习
- 将Python特有的函数写入文件
import pickle
def index():
print(f" 这是index 函数")
# 将函数对象写入文件
with open('my_func', 'wb') as f:
print(index)
# <function index at 0x0000024D28D03E20>
pickle.dump(index, f)
- 将Python特有的函数对象在文件中读出来并调用
import pickle
def index():
print(f" 这是index 函数")
# 将函数对象写入文件
with open('my_func', 'rb') as f:
data = pickle.load(f)
print(data)
# <function index at 0x0000027D9C893E20>
data()
# 这是index 函数
【7】小结
- 这时候机智的你又要说了,既然pickle如此强大,为什么还要学json呢?
- 这里我们要说明一下
- json是一种所有的语言都可以识别的数据结构。
- 如果我们将一个字典或者序列化成了一个json存在文件里
- 那么java代码或者js代码也可以拿来用。
- 但是如果我们用pickle进行序列化
- 其他语言就不能读懂这是什么了~
- 所以,如果你序列化的内容是列表或者字典,我们非常推荐你使用json模块
- 但如果出于某种原因你不得不序列化其他的数据类型,而未来你还会用python对这个数据进行反序列化的话,那么就可以使用pickle
【六】subprocess
【1】介绍
- subprocess模块允许我们启动一个新进程,并连接到它们的输入/输出/错误管道,从而获取返回值。
- 简单理解就是:使用我们自己的电脑去链接别人的电脑 (socket模块)
【2】使用
# windows系统默认的编码格式是:gbk
import subprocess
"""
1. 使用我们自己的电脑去链接别人的电脑 (socket模块)
"""
res = subprocess.Popen('tasklistaaa', shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
print(res) # <subprocess.Popen object at 0x000001ABB1970310>
# print(res.stdout.read().decode('gbk')) # tasklist执行之后的正确结果返回
print(res.stderr.read().decode('gbk'))
【3】run方法
def run(*popenargs,
input=None, capture_output=False, timeout=None, check=False, **kwargs):
args:
表示要执行的命令。
必须是一个字符串,字符串参数列表。
stdin、stdout 和 stderr:
子进程的标准输入、标准输出和标准错误。
其值可以是subprocess.PIPE
subprocess.PIPE 表示为子进程创建新的管道。
subprocess.DEVNULL
subprocess.DEVNULL表示使用 os.devnull
默认使用的是 None,表示什么都不做。
一个已经存在的文件描述符、
已经打开的文件对象
或者 None。
另外
stderr 可以合并到 stdout 里一起输出。
timeout:
设置命令超时时间。
如果命令执行时间超过timeout,
子进程将被杀死,并弹出 TimeoutExpired 异常。
check:
如果该参数设置为 True,并且进程退出状态码不是0
则弹出 CalledProcessError 异常。
encoding:
如果指定了该参数,则 stdin、stdout 和 stderr 可以接收字符串数据,并以该编码方式编码。
否则只接收 bytes 类型的数据。
shell:
如果该参数为 True
将通过操作系统的shell执行指定的命令。
【4】Pepen方法
- Popen 是 subprocess的核心,子进程的创建和管理都靠它处理。
class Popen:
""" Execute a child program in a new process.
For a complete description of the arguments see the Python documentation.
Arguments:
args: A string, or a sequence of program arguments.
bufsize: supplied as the buffering argument to the open() function when
creating the stdin/stdout/stderr pipe file objects
executable: A replacement program to execute.
stdin, stdout and stderr: These specify the executed programs' standard
input, standard output and standard error file handles, respectively.
preexec_fn: (POSIX only) An object to be called in the child process
just before the child is executed.
close_fds: Controls closing or inheriting of file descriptors.
shell: If true, the command will be executed through the shell.
cwd: Sets the current directory before the child is executed.
env: Defines the environment variables for the new process.
text: If true, decode stdin, stdout and stderr using the given encoding
(if set) or the system default otherwise.
universal_newlines: Alias of text, provided for backwards compatibility.
startupinfo and creationflags (Windows only)
restore_signals (POSIX only)
start_new_session (POSIX only)
group (POSIX only)
extra_groups (POSIX only)
user (POSIX only)
umask (POSIX only)
pass_fds (POSIX only)
encoding and errors: Text mode encoding and error handling to use for
file objects stdin, stdout and stderr.
Attributes:
stdin, stdout, stderr, pid, returncode
"""
_child_created = False # Set here since __del__ checks it
def __init__(self, args, bufsize=-1, executable=None,
stdin=None, stdout=None, stderr=None,
preexec_fn=None, close_fds=True,
shell=False, cwd=None, env=None, universal_newlines=None,
startupinfo=None, creationflags=0,
restore_signals=True, start_new_session=False,
pass_fds=(), *, user=None, group=None, extra_groups=None,
encoding=None, errors=None, text=None, umask=-1, pipesize=-1):
...
【5】call方法
def call(*popenargs, timeout=None, **kwargs):
"""Run command with arguments. Wait for command to complete or
timeout, then return the returncode attribute.
The arguments are the same as for the Popen constructor. Example:
retcode = call(["ls", "-l"])
"""
【七】hashlib
【1】什么是摘要算法
- Python的hashlib提供了常见的摘要算法
- 如MD5
- SHA1等等。
- 摘要算法又称哈希算法、散列算法。
- 它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。
- 摘要算法就是通过摘要函数f()对任意长度的数据data计算出固定长度的摘要digest
- 目的是为了发现原始数据是否被人篡改过。
- 摘要算法之所以能指出数据是否被篡改过,就是因为摘要函数是一个单向函数
- 计算f(data)很容易,但通过digest反推data却非常困难。
- 而且,对原始数据做一个bit的修改,都会导致计算出的摘要完全不同。
【2】加密数据
(1)一次性加密(同一段数据)
- 我们以常见的摘要算法MD5为例,计算出一个字符串的MD5值:
import hashlib
md5 = hashlib.md5()
md5.update(b'how to use md5 in python hashlib?')
print(md5.hexdigest())
# 计算结果如下:
# d26a53750bc40b38b65a520292f69306
(2)分次加密(同一段数据)
- 如果数据量很大,可以分块多次调用update(),最后计算的结果是一样的:
import hashlib
md5 = hashlib.md5()
md5.update(b'how to use md5 in ')
md5.update(b'python hashlib?')
print(md5.hexdigest())
# 计算结果如下:
# d26a53750bc40b38b65a520292f69306
- MD5是最常见的摘要算法,速度很快,生成结果是固定的128 bit字节,通常用一个32位的16进制字符串表示。
【3】摘要算法升级之加盐
- 任何允许用户登录的网站都会存储用户登录的用户名和口令。
- 如何存储用户名和口令呢?
- 方法是存到数据库表中:
name | password
--------+----------
michael | 123456
bob | abc999
alice | alice2008
- 如果以明文保存用户口令,如果数据库泄露,所有用户的口令就落入黑客的手里。
- 此外,网站运维人员是可以访问数据库的,也就是能获取到所有用户的口令。
- 正确的保存口令的方式是不存储用户的明文口令,而是存储用户口令的摘要,比如MD5:
username | password
---------+---------------------------------
michael | e10adc3949ba59abbe56e057f20f883e
bob | 878ef96e86145580c38c87f0410ad153
alice | 99b1c2188db85afee403b1536010c2c9
- 考虑这么个情况,很多用户喜欢用123456,888888,password这些简单的口令
- 于是,黑客可以事先计算出这些常用口令的MD5值,得到一个反推表:
'e10adc3949ba59abbe56e057f20f883e': '123456'
'21218cca77804d2ba1922c33e0151105': '888888'
'5f4dcc3b5aa765d61d8327deb882cf99': 'password'
- 这样,无需破解,只需要对比数据库的MD5,黑客就获得了使用常用口令的用户账号。
- 对于用户来讲,当然不要使用过于简单的口令。
- 但是,我们能否在程序设计上对简单口令加强保护呢?
- 由于常用口令的MD5值很容易被计算出来
- 所以,要确保存储的用户口令不是那些已经被计算出来的常用口令的MD5
- 这一方法通过对原始口令加一个复杂字符串来实现,俗称“加盐”:
hashlib.md5("salt".encode("utf8"))
- 经过Salt处理的MD5口令,只要Salt不被黑客知道,即使用户输入简单口令,也很难通过MD5反推明文口令。
- 但是如果有两个用户都使用了相同的简单口令比如123456,在数据库中,将存储两条相同的MD5值,这说明这两个用户的口令是一样的。
- 有没有办法让使用相同口令的用户存储不同的MD5呢?
- 如果假定用户无法修改登录名,就可以通过把登录名作为Salt的一部分来计算MD5,从而实现相同口令的用户也存储不同的MD5。
- 摘要算法在很多地方都有广泛的应用。
- 要注意摘要算法不是加密算法,不能用于加密(因为无法通过摘要反推明文),只能用于防篡改,但是它的单向计算特性决定了可以在不存储明文口令的情况下验证用户口令。
【4】摘要算法模版
import hashlib
def get_md5_disgest(salt, data):
data = salt + data
b_data = data.encode('utf-8')
encrypted_data = hashlib.md5(b_data)
return encrypted_data.hexdigest()
【5】SHA1
- 另一种常见的摘要算法是SHA1,调用SHA1和调用MD5完全类似:
import hashlib
sha1 = hashlib.sha1()
sha1.update(b'how to use sha1 in ')
sha1.update(b'python hashlib?')
print(sha1.hexdigest())
# 2c76b57293ce30acef38d98f6046927161b46a44
- SHA1的结果是160 bit字节,通常用一个40位的16进制字符串表示。
- 比SHA1更安全的算法是SHA256和SHA512,不过越安全的算法越慢,而且摘要长度更长。
【6】MD5加密在验证登录中的应用
import os
import hashlib
from verify_code import get_verify_code
# 声明数据库位置
file_path = 'Infos' + '\\' + 'user_pwd.txt'
if not os.path.exists(file_path):
with open(file_path, 'a') as f:
f.write('')
def encrypt_decrypt(data):
# 转为二进制数据
data = data.encode('utf-8')
# 创建md5对象
md5 = hashlib.md5()
# md5进行加密
md5.update(data)
# 取出md5加密后的哈希值
encrypt_result = md5.hexdigest()
return encrypt_result
def write_read_data(data=None, cmd=0):
if cmd == 0:
with open(file_path, 'a+') as f:
f.write(data)
else:
user_list = []
user_info_data = []
with open(file_path, 'r') as f:
for line in f:
user_data = {}
line = line.strip().split('|')
username, password, salt_code = line[0], line[1], line[2]
user_data['username'] = username
user_data['password'] = password
user_data['salt_code'] = salt_code
user_list.append(username)
user_info_data.append(user_data)
return [user_list, user_info_data]
def register(username, password):
# 获得六位数的盐
salt_code = get_verify_code(6)
# 原始密码加盐
password_str = password + salt_code
# 加盐密码加密
password_encrypted = encrypt_decrypt(password_str)
# 拼接存储数据格式
user_pwd_data = f'{username}|{password_encrypted}|{salt_code}\n'
# 写入文件存储数据
write_read_data(user_pwd_data, cmd=0)
print(f'{username}注册成功,注册结束!')
def login():
# 拿到用户名列表,用户名和密码及加盐后的列表
user_list, user_data = write_read_data(data=None, cmd=1)
username_input = input('校验Username:>>>')
password_input = input('校验Password:>>>')
# 判断用户名是否存在于用户名列表中
# 存在则继续登录
if username_input in user_list:
# 循环所有用户名及信息
for info in user_data:
# 取用户名和加密后的密码
username = info['username']
password = info['password']
# 取加盐后的密码
salt_code = info['salt_code']
# 当前密码加盐
password_str = password_input + salt_code
# 当前加盐密码加密
password_encrypted = encrypt_decrypt(password_str)
if username == username_input and password == password_encrypted:
print('登陆成功!')
return True
else:
print('用户名或密码错误,登陆失败!')
main()
else:
print('用户名不存在,请注册')
main()
def main():
# 先校验用户名和密码是否存在
username = input('Username:>>>')
# 获取用户列表
user_list = write_read_data(cmd=1)[0]
# 不存在用户信息则进行注册
if username not in user_list:
print('当前用户未注册注册,注册操作开始!')
# 注册函数
password = input('Password:>>>')
register(username, password)
# 注册完成进行二次验证校验登陆
main()
else:
password = input('Password:>>>')
# 用户存在进行登陆校验
print('进行登陆操作')
# 拿到成功的结果
res = login()
# 成功则退出
if res:
print('欢迎使用')
pass
else:
# 不成功二次校验
login()
if __name__ == '__main__':
main()
import random
'''生成六位随机 (数字 + 大小写) 验证码'''
def get_verify_code(n):
code = ''
for i in range(n):
random_int = str(random.randint(0, 9)) # 0-9之间的整数
random_upper = chr(random.randint(65, 90)) # A-Z之间的字母
random_lower = chr(random.randint(97, 122)) # a-z之间的字母
temp = random.choice([random_int, random_upper, random_lower])
code += temp
return code
if __name__ == "__main__":
res = get_verify_code(6)
print(res)
【八】logging
【1】日志模版
import logging
import logging.config
import os
import sys
try:
# 想要给日志上色就安装这个模块
import coloredlogs
except Exception as e:
if str(e) == "No module named 'coloredlogs'":
pass
# 自定义日志级别
CONSOLE_LOG_LEVEL = "INFO"
FILE_LOG_LEVEL = "DEBUG"
# 自定义日志格式
# 打印在文件里的格式:时间戳 + 线程名 + 线程ID + 任务ID + 发出日志调用的源文件名 + 发出日志调用的源代码行号 + 日志级别 + 日志消息正文
# [2023-06-04 15:16:05][MainThread:22896][task_id:root][调用.py:12][INFO][这是注册功能]
STANDARD_FORMAT = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d][%(levelname)s][%(message)s]'
# 打印在控制台的格式:日志级别 + 时间戳 + 发出日志调用的源文件名 + 发出日志调用的源代码行号 + 日志消息正文
# [INFO][2023-06-04 15:37:28,019][调用.py:12]这是注册功能
SIMPLE_FORMAT = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
'''
参数详解:
-1.%(asctime)s: 时间戳,表示记录时间
-2.%(threadName)s: 线程名称
-3.%(thread)d: 线程ID
-4.task_id:%(name)s: 任务ID,即日志记录器的名称
-5.%(filename)s: 发出日志调用的源文件名
-6.%(lineno)d: 发出日志调用的源代码行号
-7.%(levelname)s: 日志级别,如DEBUG、INFO、WARNING、ERROR、CRITICAL等
-8.%(message)s: 日志消息正文
'''
# 日志文件路径
# os.getcwd() : 获取当前工作目录,即当前python脚本工作的目录路径
# 拼接日志文件路径 : 当前工作路径 + “logs”(日志文件路径)
LOG_PATH = os.path.join(os.getcwd(), "Activity_logs")
# OS模块 : 创建多层文件夹
# exist_ok=True 的意思是如果该目录已经存在,则不会抛出异常。
os.makedirs(LOG_PATH, exist_ok=True)
# log日志文件路径 : 路径文件夹路径 + 日志文件
LOG_FILE_PATH = os.path.join(LOG_PATH, 'Logs.log')
# 日志配置字典
LOGGING_DIC = {
# 日志版本
'version': 1,
# 表示是否要禁用已经存在的日志记录器(loggers)。
# 如果设为 False,则已经存在的日志记录器将不会被禁用,而是可以继续使用。
# 如果设为 True,则已经存在的日志记录器将会被禁用,不能再被使用。
'disable_existing_loggers': False,
# 格式化程序:用于将日志记录转换为字符串以便于处理和存储。
# 格式化程序定义了每个日志记录的输出格式,并可以包括日期、时间、日志级别和其他自定义信息。
'formatters': {
# 自定义格式一:年-月-日 时:分:秒
'standard': {
# 自定义日志格式 :时间戳 + 线程名 + 线程ID + 任务ID + 发出日志调用的源文件名 + 发出日志调用的源代码行号 + 日志级别 + 日志消息正文
# 这里要对应全局的 STANDARD_FORMAT 配置
'format': STANDARD_FORMAT,
# 时间戳格式:年-月-日 时:分:秒
'datefmt': '%Y-%m-%d %H:%M:%S' # 时间戳格式
},
# 自定义格式二:
'simple': {
# 自定义日志格式:# 日志级别 + 时间戳 + 发出日志调用的源文件名 + 发出日志调用的源代码行号 + 日志消息正文
# 这里要对应全局的 SIMPLE_FORMAT 配置
'format': SIMPLE_FORMAT
},
},
# 过滤器
'filters': {},
# 日志处理器
'handlers': {
# 自定义处理器名称 - 输出到控制台屏幕
'console': {
# 设置日志等级 为INFO
'level': CONSOLE_LOG_LEVEL,
# 表示该处理器将输出日志到流(stream):日志打印到控制台
'class': 'logging.StreamHandler',
# 日志打印格式:日志级别 + 时间戳 + 发出日志调用的源文件名 + 发出日志调用的源代码行号 + 日志消息正文
# 这里的配置要对应 formatters 中的 simple 配置
'formatter': 'simple'
},
# 自定义处理器名称 - 输出到文件
'default': {
# 自定义日志等级
'level': FILE_LOG_LEVEL,
# 标准输出到文件
'class': 'logging.handlers.RotatingFileHandler',
# 日志打印格式:年-月-日 时:分:秒
# 这里的配置要对应 formatters 中的 standard 配置
'formatter': 'standard',
# 这里 要注意声明配置文件输出端的文件路径
'filename': LOG_FILE_PATH,
# 限制文件大小:1024 * 1024 * 5 = 5242880,意味着这个变量的值是 5MB(兆字节)
'maxBytes': 1024 * 1024 * 5,
# 表示保留最近的5个日志文件备份。
# 当日志文件达到最大大小限制时,将会自动轮转并且保留最新的5个备份文件,以便查看先前的日志记录。
# 当日志文件达到最大大小限制时,会自动进行轮转,后续的文件名将会以数字进行命名,
# 例如,第一个备份文件将被重命名为原始日志文件名加上".1"的后缀,
# 第二个备份文件将被重命名为原始日志文件名加上“.2”的后缀,
# 以此类推,直到保留的备份数量达到设定的最大值。
'backupCount': 5,
# 日志存储文件格式
'encoding': 'utf-8',
},
},
# 日志记录器,用于记录应用程序的运行状态和错误信息。
'loggers': {
# 空字符串作为键 能够兼容所有的日志(当没有找到对应的日志记录器时默认使用此配置)
# 默认日志配置
'': {
# 日志处理器 类型:打印到控制台输出 + 写入本地日志文件
'handlers': ['default', 'console'],
# 日志等级 : DEBUG
'level': 'DEBUG',
# 默认情况下,当一个日志消息被发送到一个Logger对象且没有被处理时,该消息会被传递给它的父Logger对象,以便在更高层次上进行处理。
# 这个传递过程称为“传播(propagation)”,而propagate参数指定了是否要使日志消息向上传播。
# 将其设置为True表示应该传播消息到上一级的Logger对象;如果设置为False则不传播。
# 表示异常将会在程序中继续传播
# 也就是说,如果一个异常在当前的代码块中没有被处理,它将会在上级代码块或调用函数中继续向上传递,直到被某个代码块捕获或者程序退出。
# 这是 Python 中异常处理机制的默认行为。
# 如果将 'propagate' 设置为 False,则异常不会被传播,即使在上级代码块中没有处理异常的语句,程序也会忽略异常并继续执行。
'propagate': True,
},
},
}
def set_logging_color(name='', ):
# 初始化日志处理器 - 使用配置字典初始化日志处理器(将自定义配置加载到日志处理器中)
# logging.basicConfig(level=logging.WARNING)
logging.config.dictConfig(LOGGING_DIC)
# 实例化日志处理器对象 - 并赋予日志处理器等级
logger = logging.getLogger(name)
# 将logger对象传递给coloredlogs.install()函数,并执行该函数以安装彩色日志记录器,使日志信息在控制台上呈现为带有颜色的格式。
# 具体来说,该函数会使用ANSI转义序列在终端上输出日志级别、时间戳和消息等信息,并按日志级别使用不同的颜色来区分它们。这可以让日志信息更易于阅读和理解。
coloredlogs.install(logger=logger)
# 禁止日志消息向更高级别的父记录器(如果存在)传递。
# 通常情况下,当一个记录器发送一条日志消息时,该消息会被传递给其所有祖先记录器,直到传递到根记录器为止。但是,通过将logger.propagate设置为False,就可以阻止该记录器的消息向上层传递。
# 换句话说,该记录器的消息只会被发送到该记录器的处理程序(或子记录器)中,而不会传递给祖先记录器,即使祖先记录器的日志级别比该记录器要低。
# 这种方法通常适用于需要对特定记录器进行控制,并希望完全独立于其祖先记录器的情况。
# 确保 coloredlogs 不会将我们的日志事件传递给根 logger,这可以防止我们重复记录每个事件
logger.propagate = False
# 配置 日志颜色
# 这段代码定义了一个名为coloredFormatter的变量,并将其赋值为coloredlogs.ColoredFormatter。
# 这是一个Python库中的类,用于创建带有彩色日志级别和消息的格式化器。
# 该变量可以用作日志记录器或处理程序的格式化程序,以使日志输出更易于阅读和理解。
coloredFormatter = coloredlogs.ColoredFormatter(
# fmt表示格式字符串,它包含了一些占位符,用于在记录日志时动态地填充相关信息。
# [%(name)s]表示打印日志时将记录器的名称放在方括号内,其中name是一个变量名,将被记录器名称所替换。
# %(asctime)s表示打印日志时将时间戳(格式化为字符串)插入到消息中,其中asctime是时间的字符串表示形式。
# %(funcName)s表示打印日志时将函数名插入到消息中,其中funcName是函数的名称。
# %(lineno)-3d表示打印日志时将行号插入到消息中,并且将其格式化为3位数字,其中lineno表示行号。
# %(message)s表示打印日志时将消息本身插入到消息中。
# 综合起来,这个格式字符串将在记录日志时输出以下信息:记录器名称、时间戳、函数名称、行号和日志消息。
# 记录器名称 + 时间戳 + 函数名称 + 行号 + 日志消息
# [root] 2023-06-04 16:00:57 register 15 this is an info message
fmt='[%(name)s] %(asctime)s %(funcName)s %(lineno)-3d %(message)s',
# 级别颜色字典
level_styles=dict(
# debug 颜色:白色
debug=dict(color='white'),
# info 颜色:蓝色
info=dict(color='blue'),
# warning 颜色:黄色 且 高亮
warning=dict(color='yellow', bright=True),
# error 颜色:红色 且 高亮 且 加粗
error=dict(color='red', bold=True, bright=True),
# critical 颜色:灰色 且 高亮 且 背景色为红色
critical=dict(color='black', bold=True, background='red'),
),
# 这段代码定义了一个名为field_styles的字典 , 其中包含四个键值对。
# 每个键代表日志记录中的不同字段,而每个值是一个字典,它指定了与该字段相关联的样式选项。
# 具体来说,这些字段和样式选项如下:
# name:指定记录器的名称,将使用白色颜色。
# asctime:指定日志记录的时间戳,将使用白色颜色。
# funcName:指定记录消息的函数名称,将使用白色颜色。
# lineno:指定记录消息的源代码行号,将使用白色颜色。
field_styles=dict(
name=dict(color='white'),
asctime=dict(color='white'),
funcName=dict(color='white'),
lineno=dict(color='white'),
)
)
## 配置 StreamHandler:终端打印界面
# 这行代码定义了一个名为ch的日志处理器。
# 具体来说,它是logging.StreamHandler类的一个实例,用于将日志输出到标准输出流(即控制台)中。
# 在创建StreamHandler对象时,需要指定要使用的输出流
# 因此stream=sys.stdout参数指定了该对象将把日志写入到标准输出流中.
ch = logging.StreamHandler(stream=sys.stdout)
# 这段代码是 Python 中用于设置日志输出格式的语句。
# 它使用了一个名为 coloredFormatter 的格式化器对象,并将其传递给 ch.setFormatter() 方法来指定输出日志的样式。
# 具体来说,ch 是一个 Logger 对象,它代表了整个日志系统中的一个记录器。
# 可以通过该对象来控制日志的级别、输出位置等行为。而 setFormatter() 方法则用于设置该 Logger 输出日志的格式化方式
# 这里传递的是 coloredFormatter 格式化器对象。
# 在实际应用中,coloredFormatter 可以是自定义的 Formatter 类或者已经存在的 Formatter 对象。
# 这里 将 coloredFormatter - 彩色输出日志信息的格式化器 传入进去。
ch.setFormatter(fmt=coloredFormatter)
# 这段代码是在Python中添加一个日志记录器(logger)的处理器(handler)。其中,hdlr参数是指定要添加到记录器(logger)中的处理器(handler)对象,ch是一个代表控制台输出的处理器对象。
# 这行代码的作用是将控制台输出的信息添加到日志记录器中。
logger.addHandler(hdlr=ch)
# 这段代码用于设置日志级别为DEBUG,也就是最低级别的日志记录。
# 意思是在程序运行时,只有DEBUG级别及以上的日志信息才会被记录并输出,而比DEBUG级别更低的日志信息则不会被记录或输出。
# DEBUG(调试)、INFO(信息)、WARNING(警告)、ERROR(错误)和CRITICAL(严重错误)。
logger.setLevel(level=logging.DEBUG)
# 返回日志生成对象
return logger
def get_logger(name='', ):
'''
:param name: 日志等级
:return:
'''
# 初始化日志处理器 - 使用配置字典初始化日志处理器(将自定义配置加载到日志处理器中)
logging.config.dictConfig(LOGGING_DIC)
# 实例化日志处理器对象 - 并赋予日志处理器等级
logger = logging.getLogger(name)
# 返回日志生成对象
return logger
if __name__ == "__main__":
# # 示例:
# # (1)初始化日志处理器 - 使用配置字典初始化日志处理器(将自定义配置加载到日志处理器中)
# logging.config.dictConfig(LOGGING_DIC)
# # (2)实例化日志处理器对象 - 并赋予日志处理器等级
# # # logger1 = logging.getLogger('') # 默认为 '' 即以默认配置进行实例化
# logger1 = logging.getLogger('')
# # (3)当日志发生时,打印的提示语
# logger1.debug('这是日志生成语句')
logger_nor = get_logger()
logger_nor.info(msg="this is a debug message")
logger_col = set_logging_color()
logger_col.debug(msg="this is a debug message")
标签:常用,模块,Python,file,time,print,path,日志,os
From: https://www.cnblogs.com/Fredette/p/17967823