常用模块
random模块
import random #随机小数 print(random.random()) # 大于0且小于1之间的小数 print(random.uniform(1,3)) #大于1小于3的小数 #随机整数 print(random.randint(1,5)) # 大于等于1且小于等于5之间的整数 print(random.randrange(1,10,2)) # 大于等于1且小于10之间的奇数 #随机选择一个返回 print(random.choice([1,'123',[4,5]])) #1或者123或者[4,5] #随机选择多个返回,返回的个数为函数的第二个参数 print(random.sample([1,'123',[4,5]],2)) #列表元素任意2个组合 #打乱列表顺序 item=[1,3,5,7,9] random.shuffle(item) # 打乱次序,没有返回值,返回None print(item)
生成随机验证码
#生成验证码 import random def v_code(): code = '' for i in range(5): num=random.randint(0,9) #随机取一个0-9的整数 ALF=chr(random.randint(65,90)) #随机取一个大写字母,通过ASCII码值,使用chr转换成字符 alf = chr(random.randint(97,122)) #随机取一个小写字母,通过ASCII码值,使用chr转换成字符 add=random.choice([num,alf,ALF]) #随机选择一个元素 code="".join([code,str(add)]) #通过join方法生成一个新的字符串 return code print(v_code())
时间有关的模块
time模块
表示时间的三种方式
在Python中,通常有这三种方式来表示时间:时间戳、元组(struct_time)、格式化的时间字符串:
(1)时间戳(timestamp) :通常来说,时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量。我们运行“type(time.time())”,返回的是float类型。
(2)格式化的时间字符串(Format String): ‘1999-12-06’
%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 当前时区的名称 %% %号本身
(3)元组(struct_time) :struct_time元组共有9个元素共九个元素:(年,月,日,时,分,秒,一年中第几周,一年中第几天等)
索引(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 |
Python中表示时间的几种格式
import time #常用方法 time.sleep(secs) #(线程)推迟指定的时间运行。单位为秒。 # 时间戳 print(time.time()) # >> 1597245663.4838898 # 时间字符串 print(time.strftime('%Y-%m-%d %H:%M:%S')) # 默认当前时间 # >> 2020-08-12 23:21:03 print(time.strftime('%Y-%m-%d %X')) # >> 2020-08-12 23:21:03 print(time.strftime('%Y-%m-%d', time.localtime())) # >> 2020-08-12 # 时间元组:localtime将一个时间戳转换为当前时区的struct_time print(time.localtime()) ''' time.struct_time(tm_year=2020, tm_mon=8, tm_mday=12, tm_hour=23, tm_min=21, tm_sec=3, tm_wday=2, tm_yday=225, tm_isdst=0) '''
小结:时间戳是计算机能够识别的时间;时间字符串是人能够看懂的时间;元组则是用来操作时间的
几种格式之间的转换
# 格式化时间 ----> 结构化时间 ft = time.strftime('%Y/%m/%d %H:%M:%S') st = time.strptime(ft,'%Y/%m/%d %H:%M:%S') print(st) # 结构化时间 ---> 时间戳 t = time.mktime(st) print(t) # 时间戳 ----> 结构化时间 t = time.time() st = time.localtime(t) print(st) # 结构化时间 ---> 格式化时间 ft = time.strftime('%Y/%m/%d %H:%M:%S',st) print(ft)
#结构化时间 --> %a %b %d %H:%M:%S %Y串 #time.asctime(结构化时间) 如果不传参数,直接返回当前时间的格式化串 >>>time.asctime(time.localtime(1500000000)) 'Fri Jul 14 10:40:00 2017' >>>time.asctime() 'Mon Jul 24 15:18:33 2017' #时间戳 --> %a %d %d %H:%M:%S %Y串 #time.ctime(时间戳) 如果不传参数,直接返回当前时间的格式化串 >>>time.ctime() 'Mon Jul 24 15:19:07 2017' >>>time.ctime(1500000000) 'Fri Jul 14 10:40:00 2017' t = time.time() ft = time.ctime(t) print(ft) st = time.localtime() ft = time.asctime(st) print(ft)
import time true_time=time.mktime(time.strptime('2017-09-11 08:30:00','%Y-%m-%d %H:%M:%S')) time_now=time.mktime(time.strptime('2017-09-12 11:00:00','%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_min,struct_time.tm_sec))
datetime模块
datetime模块用于是date和time模块的合集,datetime有两个常量,MAXYEAR和MINYEAR,分别是9999和1
import datetime print(datetime.MAXYEAR) # 9999 print(datetime.MINYEAR) # 1
datetime模块定义了5个类,分别是
1、 datetime.date: 是指年月日构成的日期(相当于日历)
2、datetime.time: 是指时分秒微秒构成的一天24小时中的具体时间(相当于手表)
3、datetime.datetime: 上面两个合在一起,既包含时间又包含日期
4、datetime.timedelta: 时间间隔对象(timedelta)。一个时间点(datetime)加上一个时间间隔(timedelta)可以得到一个新的时间点(datetime)。比如今天的上午3点加上5个小时得到今天的上午8点。同理,两个时间点相减会得到一个时间间隔
5、datetime.tzinfo:时区的相关信息
datetime类(日期时间)
datetime类有很多参数,datetime(year, month, day[, hour[, minute[, second[, microsecond[,tzinfo]]]]]),返回年月日,时分秒
import datetime d = datetime.datetime(2019, 11, 20, 20, 20, 20) # 获取指定日期和时间 print(d) # >> 2019-11-20 20:20:20 print(d.year) # 获取年 print(d.month) # 月 print(d.day) # 日 print(d.hour) # 时 print(d.minute) # 分 print(d.second) # 秒 # 获取当前系统时间日期和时间 print(datetime.datetime.now()) # >> 2020-08-13 22:20:56.388552 # 获取当前系统日期 print(datetime.datetime.now().date()) # >> 2020-08-13 # 获取当前系统时间 print(datetime.datetime.now().time()) # >> 22:20:56.388552 # 由日期格式转化为字符串格式 print(datetime.datetime.now().strftime('%b-%d-%Y %H:%M:%S')) # >> Aug-13-2020 22:20:56 # 由字符串格式转化为日期格式 print(datetime.datetime.strptime('Aug-13-2020 22:20:56', '%b-%d-%Y %H:%M:%S')) # >> 2020-08-13 22:20:56
date类(日期的类)
date类有三个参数,datetime.date(year,month,day),返回year-month-day
import datetime d = datetime.date(2018, 10, 11) print(d) # 2018-10-11 print(d.year) # 年 print(d.month) # 月 print(d.day) # 日 # 返回当前日期 t = datetime.date.today() print(t) # >> 2020-08-13 # 把日期时间按照给定的format进行格式化 print(datetime.date.strftime(t, '%Y/%m/%d %H:%M:%S')) # >> 2020/08/13 00:00:00 print(datetime.date.today().strftime('%Y/%m/%d %H:%M:%S')) # >> 2020/08/13 00:00:00 # 返回日期对应的time.struct_time对象 print(datetime.date.today().timetuple()) ''' time.struct_time(tm_year=2020, tm_mon=8, tm_mday=13, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=226, tm_isdst=-1) ''' # datetime.date.timple() 转成struct_time格式,这样传递给time.mktime(t) 后,直接转成时间戳格式 import datetime, time print(time.mktime(datetime.date.today().timetuple())) # 1550851200.0 # 返回一个替换后的date对象 t = datetime.date.today() print(t) # 2019-02-23 print(datetime.date.replace(t, year=2020)) # 2020-02-23 print(datetime.date.today().replace(year=2020)) # 2020-02-23 # 将时间戳转化为date对象 print(datetime.date.fromtimestamp(1000000)) # 1970-01-12 # 返回格式如:Sat Feb 23 00:00:00 2019 t = datetime.date.today() print(datetime.date.ctime(t)) # Sat Feb 23 00:00:00 2019 print(datetime.date.today().ctime()) # Sat Feb 23 00:00:00 2019 # 返回格式如YYYY-MM-DD t = datetime.date.today() print(datetime.date.isoformat(t)) # 2019-02-23 print(datetime.date.today().isoformat()) # 2019-02-23 # 返回格式如(year,month,day)的元组 t = datetime.date.today() print(datetime.date.isocalendar(t)) # (2019, 8, 6) print(datetime.date.today().isocalendar()) # (2019, 8, 6) # 返回指定日期的星期(1-7),星期一=1,星期日=7 t = datetime.date.today() print(datetime.date.isoweekday(t)) # 6 print(datetime.date.today().isoweekday()) # 6 # 返回日期的星期,星期一=0,星期日=6 t = datetime.date(2019, 2, 23) print(datetime.date.weekday(t)) # 5 print(datetime.date.today().weekday()) #
%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 当前时区的名称 %% %号本身
date对象中小时、分钟、秒默认都是0,纪元年的那个时间
time类(时间的类)
datetime.time(hour=0, minute=0, second=0, microsecond=0, tzinfo=None)
所有的参数都是可选的;tzinfo 可以是 None 或者 tzinfo 子类的实例对象;其余的参数可以是整数,并且在以下范围内:
- 0 <= hour < 24
- 0 <= minute < 60
- 0 <= second < 60
- 0 <= microsecond < 1000000
注:如果参数超出范围,将引发 ValueError 异常
import datetime t = datetime.time(12, 15, 52) print(t) # 12:15:52 print(t.hour) # 时 print(t.minute) # 分 print(t.second) # 秒 print(t.microsecond) # 毫秒 # 格式化输出,time对应的年、月、日为1900、01、01,纪元年的那个时间 t = datetime.time(8, 12, 20) print(datetime.time.strftime(t, '%Y-%m-%d %H:%M:%S')) # 1900-01-01 08:12:20 print(datetime.time(8, 12, 20).strftime('%Y-%m-%d %H:%M:%S')) # 1900-01-01 08:12:20 # 生成一个新的时间对象,用参数指定时间代替原有对象中的属性 t = datetime.time(8, 12, 20) print(datetime.time.replace(t, hour=2)) # 02:12:20 print(datetime.time(8, 12, 20).replace(hour=2)) # 02:12:20
timedelta类(时间间隔)
用于计算两个日期之间的差值,对日期和时间进行加减实际上就是把datetime往后或往前计算,得到新的datetime。加减可以直接用+
和-
运算符
datetime.timedelta(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0)
注意参数中没有年的选项,可以使用 weeks=52 来表示一年
days:天,seconds:秒,microseconds:微秒,milliseconds:毫秒,minutes:分钟,hours:小时,weeks:周期
import datetime now = datetime.datetime.now() # 当前时间 print(now) # 2020-08-13 22:53:17.707672 # 时间间隔 d7 = datetime.timedelta(weeks=52) ret = now + d7 print(ret) # 2021-08-12 22:53:17.707672
os模块
和操作系统打交道的模块
import os # 当前执行这个python文件的工作目录相关的工作路径 os.getcwd() # 获取当前工作目录,即当前python脚本工作的目录路径 os.chdir("dirname") # 改变当前脚本工作目录;相当于shell下cd print(os.curdir) # 返回当前目录: ('.') print(os.pardir) # 获取当前目录的父目录字符串名:('..') # 和文件夹相关 os.makedirs('dirname1/dirname2') # 可生成多层递归目录 os.removedirs('dirname1') # 若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推 os.mkdir('dirname') # 生成单级目录;相当于shell中mkdir dirname os.rmdir('dirname') # 删除单级空目录,若目录不为空则无法删除,报错;相当于shell中rmdir dirname os.listdir('dirname') # 列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印 # 和文件相关 os.remove() # 删除一个文件 os.rename("oldname", "newname") # 重命名文件/目录 os.stat('path/filename') # 获取文件/目录信息 # 和操作系统差异相关 print(os.sep) # 输出操作系统特定的路径分隔符,win下为"\\",Linux下为"/" print(os.linesep) # 输出当前平台使用的行终止符,win下为"\t\n",Linux下为"\n" print(os.pathsep) # 输出用于分割文件路径的字符串 win下为;,Linux下为: print(os.name) # 输出字符串指示当前使用平台。win->'nt'; Linux->'posix' # 和执行系统命令相关 os.system("bash command") # 运行shell命令,直接显示 os.popen("bash command").read() #运行shell命令,获取执行结果 print(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.split(path)的第二个元素。 os.path.exists(path) #如果path存在,返回True;如果path不存在,返回False os.path.isabs(path) #如果path是绝对路径,返回True os.path.isfile(path) #如果path是一个存在的文件,返回True。否则返回False os.path.isdir(path) #如果path是一个存在的目录,则返回True。否则返回False 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模块
和python解释器打交道的模块
import sys sys.argv # 命令行参数List,第一个元素是程序本身路径 sys.exit(n) # 退出程序,正常退出时exit(0),错误退出sys.exit(1) sys.version # 获取Python解释程序的版本信息 sys.path # 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值 sys.platform # 返回操作系统平台名称
序列化模块
什么叫序列化——将原本的字典、列表等内容转换成一个字符串的过程就叫做序列化。
比如,我们在python代码中计算的一个数据需要给另外一段程序使用,那我们怎么给? 现在我们能想到的方法就是存在文件里,然后另一个python程序再从文件里读出来。 但是我们都知道,对于文件来说是没有字典这个概念的,所以我们只能将数据转换成字典放到文件中。 你一定会问,将字典转换成一个字符串很简单,就是str(dic)就可以办到了,为什么我们还要学习序列化模块呢? 没错序列化的过程就是从dic 变成str(dic)的过程。现在你可以通过str(dic),将一个名为dic的字典转换成一个字符串, 但是你要怎么把一个字符串转换成字典呢? 聪明的你肯定想到了eval(),如果我们将一个字符串类型的字典str_dic传给eval,就会得到一个返回的字典类型了。 eval()函数十分强大,但是eval是做什么的?e官方demo解释为:将字符串str当成有效的表达式来求值并返回计算结果。 BUT!强大的函数有代价。安全性是其最大的缺点。 想象一下,如果我们从文件中读出的不是一个数据结构,而是一句"删除文件"类似的破坏性语句,那么后果实在不堪设设想。 而使用eval就要担这个风险。 所以,我们并不推荐用eval方法来进行反序列化操作(将str转换成python中的数据结构)
序列化的目的
json模块
JSON和Python内置的数据类型对应如下:
Json模块提供了四个功能:dumps、dump、loads、load
1、loads和dumps
import json dic = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'} str_dic = json.dumps(dic) # 序列化:将一个字典转换成一个字符串 print(type(str_dic), str_dic) # >> <class 'str'> {"k3": "v3", "k1": "v1", "k2": "v2"} # 注意,json转换完的字符串类型的字典中的字符串是由""表示的 dic2 = json.loads(str_dic) # 反序列化:将一个字符串格式的字典转换成一个字典 # 注意,要用json的loads功能处理的字符串类型的字典中的字符串必须由""表示 print(type(dic2), dic2) # >> <class 'dict'> {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'} list_dic = [1, ['a', 'b', 'c'], 3, {'k1': 'v1', 'k2': 'v2'}] str_dic = json.dumps(list_dic) # 也可以处理嵌套的数据类型 print(type(str_dic), str_dic) # >> <class 'str'> [1, ["a", "b", "c"], 3, {"k1": "v1", "k2": "v2"}] list_dic2 = json.loads(str_dic) print(type(list_dic2), list_dic2) # >> <class 'list'> [1, ['a', 'b', 'c'], 3, {'k1': 'v1', 'k2': 'v2'}]
2、load和dump
import json f = open('json_file','w') dic = {'k1':'v1','k2':'v2','k3':'v3'} json.dump(dic,f) #dump方法接收一个文件句柄,直接将字典转换成json字符串写入文件 f.close() f = open('json_file') dic2 = json.load(f) #load方法接收一个文件句柄,直接将文件中的json字符串转换成数据结构返回 f.close() print(type(dic2),dic2)
Serialize obj to a JSON formatted str.(字符串表示的json对象) Skipkeys:默认值是False,如果dict的keys内的数据不是python的基本类型(str,unicode,int,long,float,bool,None),设置为False时,就会报TypeError的错误。此时设置成True,则会跳过这类key ensure_ascii:,当它为True的时候,所有非ASCII码字符显示为\uXXXX序列,只需在dump时将ensure_ascii设置为False即可,此时存入json的中文即可正常显示。) If check_circular is false, then the circular reference check for container types will be skipped and a circular reference will result in an OverflowError (or worse). If allow_nan is false, then it will be a ValueError to serialize out of range float values (nan, inf, -inf) in strict compliance of the JSON specification, instead of using the JavaScript equivalents (NaN, Infinity, -Infinity). indent:应该是一个非负的整型,如果是0就是顶格分行显示,如果为空就是一行最紧凑显示,否则会换行且按照indent的数值显示前面的空白分行显示,这样打印出来的json数据也叫pretty-printed json separators:分隔符,实际上是(item_separator, dict_separator)的一个元组,默认的就是(‘,’,’:’);这表示dictionary内keys之间用“,”隔开,而KEY和value之间用“:”隔开。 default(obj) is a function that should return a serializable version of obj or raise TypeError. The default simply raises TypeError. sort_keys:将数据根据keys的值进行排序。 To use a custom JSONEncoder subclass (e.g. one that overrides the .default() method to serialize additional types), specify it with the cls kwarg; otherwise JSONEncoder is used.
import json data = {'username':['李华','二愣子'],'sex':'male','age':16} json_dic2 = json.dumps(data,sort_keys=True,indent=2,separators=(',',':'),ensure_ascii=False) print(json_dic2)
pickle模块
用于序列化的两个模块
- json,用于字符串 和 python数据类型间进行转换
- pickle,用于python特有的类型 和 python的数据类型间进行转换
pickle模块提供了四个功能:dumps、dump(序列化,存)、loads(反序列化,读)、load (不仅可以序列化字典,列表...可以把python中任意的数据类型序列化)
import pickle import time dic = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'} str_dic = pickle.dumps(dic) print(str_dic) # 一串二进制内容 str_dic2 = pickle.loads(str_dic) print(str_dic2) # 字典 str_time = time.localtime(10000) print(str_time) with open('pickle_file', 'wb') as f: pickle.dump(str_time, f) with open('pickle_file', 'rb') as f: str_time2 = pickle.load(f) print(str_time2)
这时候机智的你又要说了,既然pickle如此强大,为什么还要学json呢?
json是一种所有的语言都可以识别的数据结构。
如果我们将一个字典或者序列化成了一个json存在文件里,那么java代码或者js代码也可以拿来用。
但是如果我们用pickle进行序列化,其他语言就不能读懂这是什么了~
所以,如果你序列化的内容是列表或者字典,我们非常推荐你使用json模块
但如果出于某种原因你不得不序列化其他的数据类型,而未来你还会用python对这个数据进行反序列化的话,那么就可以使用pickle
shelve模块
shelve也是python提供给我们的序列化工具,比pickle用起来更简单一些。
shelve只提供给我们一个open方法,是用key来访问的,使用起来和字典类似。
import shelve f = shelve.open('shelve_file') f['key'] = {'int':10, 'float':9.5, 'string':'Sample data'} #直接对文件句柄操作,就可以存入数据 f.close() import shelve f1 = 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() f2 = shelve.open('shelve_file', writeback=True) print(f2['key']) f2['key']['new_value'] = 'this was not here before' f2.close()
writeback方式有优点也有缺点。优点是减少了我们出错的概率,并且让对象的持久化对用户更加的透明了;但这种方式并不是所有的情况下都需要,首先,使用writeback以后,shelf在open()的时候会增加额外的内存消耗,并且当DB在close()的时候会将缓存中的每一个对象都写入到DB,这也会带来额外的等待时间。因为shelve没有办法知道缓存中哪些对象修改了,哪些对象没有修改,因此所有的对象都会被写入。
hashlib模块
算法介绍
Python的hashlib提供了常见的摘要算法,如MD5,SHA1,SHA224, SHA256, SHA384, SHA512等算法。
什么是摘要算法呢?摘要算法又称哈希算法、散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。
摘要算法就是通过摘要函数f()对任意长度的数据data计算出固定长度的摘要digest,目的是为了发现原始数据是否被人篡改过。
摘要算法之所以能指出数据是否被篡改过,就是因为摘要函数是一个单向函数,计算f(data)很容易,但通过digest反推data却非常困难。而且,对原始数据做一个bit的修改,都会导致计算出的摘要完全不同。
我们以常见的摘要算法MD5为例,计算出一个字符串的MD5值:
import hashlib md5 = hashlib.md5() md5.update('abc'.encode('utf-8')) print(md5.hexdigest()) # 900150983cd24fb0d6963f7d28e17f72
如果数据量很大,可以分块多次调用update(),最后计算的结果是一样的:
import hashlib md5 = hashlib.md5() md5.update('ab'.encode('utf-8')) md5.update('c'.encode('utf-8')) print(md5.hexdigest()) # 900150983cd24fb0d6963f7d28e17f72
MD5是最常见的摘要算法,速度很快,生成结果是固定的128 bit字节,通常用一个32位的16进制字符串表示。另一种常见的摘要算法是SHA1,调用SHA1和调用MD5完全类似:
import hashlib sha1 = hashlib.sha1() sha1.update('abc'.encode('utf-8')) print(sha1.hexdigest()) # a9993e364706816aba3e25717850c26c9cd0d89d
SHA1的结果是160 bit字节,通常用一个40位的16进制字符串表示。比SHA1更安全的算法是SHA256和SHA512,不过越安全的算法越慢,而且摘要长度更长。
摘要算法应用
任何允许用户登录的网站都会存储用户登录的用户名和口令。如何存储用户名和口令呢?方法是存到数据库表中:
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。
摘要算法在很多地方都有广泛的应用。要注意摘要算法不是加密算法,不能用于加密(因为无法通过摘要反推明文),只能用于防篡改,但是它的单向计算特性决定了可以在不存储明文口令的情况下验证用户口令。
#=========知识储备========== #进度条的效果 [# ] [## ] [### ] [#### ] #指定宽度 print('[%-15s]' %'#') print('[%-15s]' %'##') print('[%-15s]' %'###') print('[%-15s]' %'####') #打印% print('%s%%' %(100)) #第二个%号代表取消第一个%的特殊意义 #可传参来控制宽度 print('[%%-%ds]' %50) #[%-50s] print(('[%%-%ds]' %50) %'#') print(('[%%-%ds]' %50) %'##') print(('[%%-%ds]' %50) %'###') #=========实现打印进度条函数========== import sys 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=1025 recv_size=0 while recv_size < data_size: time.sleep(0.1) #模拟数据的传输延迟 recv_size+=1024 #每次收1024 percent=recv_size/data_size #接收的比例 progress(percent,width=70) #进度条的宽度70
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) # 1 print(p.y) # 2
类似的,如果要用坐标和半径表示一个圆,也可以用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是无序的 od = OrderedDict([('a', 1), ('b', 2), ('c', 3)]) # OrderedDict的Key是有序的 print(od) # OrderedDict([('a', 1), ('b', 2), ('c', 3)])
注意,OrderedDict
的Key会按照插入的顺序排列,不是Key本身排序:
from collections import OrderedDict od = OrderedDict() od['z'] = 1 od['y'] = 2 od['x'] = 3 print(od) # OrderedDict([('z', 1), ('y', 2), ('x', 3)]) print(od.keys()) # 按照插入的Key的顺序返回,odict_keys(['z', 'y', 'x'])
defaultdict
有如下值集合 [11,22,33,44,55,66,77,88,99,90...],将所有大于 66 的值保存至字典的第一个key中,将小于 66 的值保存至第二个key的值中。 即: {'k1': 大于66 , 'k2': 小于66}
原生字典的解决方法
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) for value in values: if value>66: my_dict['k1'].append(value) else: my_dict['k2'].append(value)
使用dict
时,如果引用的Key不存在,就会抛出KeyError
。如果希望key不存在时,返回一个默认值,就可以用defaultdict
:
from collections import defaultdict dd = defaultdict(lambda: 'N/A') dd['key1'] = 'abc' print(dd['key1']) # key1存在,打印:abc print(dd['key2']) # # key2不存在,返回默认值:N/A
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})
re模块
正则表达式
正则表达式本身也和python没有什么关系,就是匹配字符串内容的一种规则。
官方定义:正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合
元字符 |
匹配内容 |
\w | 匹配字母(包含中文)或数字或下划线 |
\W | 匹配非字母(包含中文)或数字或下划线 |
\s | 匹配任意的空白符 |
\S | 匹配任意非空白符 |
\d | 匹配数字 |
\D | 匹配非数字 |
\A | 从字符串开头匹配 |
\z | 匹配字符串的结束,如果是换行,只匹配到换行前的结果 |
\n | 匹配一个换行符 |
\t | 匹配一个制表符 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结尾 |
. | 匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符。 |
[...] | 匹配字符组中的字符 |
[^...] | 匹配除了字符组中的字符的所有字符 |
* | 匹配0个或者多个左边的字符。 |
+ | 匹配一个或者多个左边的字符。 |
? | 匹配0个或者1个左边的字符,非贪婪方式。 |
{n} | 精准匹配n个前面的表达式。 |
{n,m} | 匹配n到m次由前面的正则表达式定义的片段,贪婪方式 |
a|b | 匹配a或者b。 |
() | 匹配括号内的表达式,也表示一个组 |
转义符 \
在正则表达式中,有很多有特殊意义的是元字符,比如\n和\s等,如果要在正则中匹配正常的"\n"而不是"换行符"就需要对"\"进行转义,变成'\\'。
在python中,无论是正则表达式,还是待匹配的内容,都是以字符串的形式出现的,在字符串中\也有特殊的含义,本身还需要转义。所以如果匹配一次"\n",字符串中要写成'\\n',那么正则里就要写成"\\\\n",这样就太麻烦了。这个时候我们就用到了r'\n'这个概念,此时的正则是r'\\n'就可以了。
正则 | 待匹配字符 | 匹配 结果 |
说明 |
\n | \n | False |
因为在正则表达式中\是有特殊意义的字符,所以要匹配\n本身,用表达式\n无法匹配 |
\\n | \n | True |
转义\之后变成\\,即可匹配 |
"\\\\n" | '\\n' | True |
如果在python中,字符串中的'\'也需要转义,所以每一个字符串'\'又需要转义一次 |
r'\\n' | r'\n' | True |
在字符串之前加r,让整个字符串不转义 |
贪婪匹配
贪婪匹配:在满足匹配时,匹配尽可能长的字符串,默认情况下,采用贪婪匹配。
# 贪婪匹配 # 在量词范围允许的情况下,尽量多的匹配内容 # .*x 表示匹配任意字符 任意多次数 遇到最后一个x才停下来 # 非贪婪(惰性)匹配 # .*?x 表示匹配任意字符 任意多次数 但是一旦遇到x就停下来
几个常用的非贪婪匹配
*? 重复任意次,但尽可能少重复 +? 重复1次或更多次,但尽可能少重复 ?? 重复0次或1次,但尽可能少重复 {n,m}? 重复n到m次,但尽可能少重复 {n,}? 重复n次以上,但尽可能少重复
re模块下的常用方法
import re # findall返回所有满足匹配条件的结果,放在列表里 ret = re.findall('\d', '1974sadaf93010dsafsa') print(ret) # ['1', '9', '7', '4', '9', '3', '0', '1', '0'] # search函数会在字符串内查找模式匹配,只到找到第一个匹配然后返回一个包含匹配信息的对象,该对象可以 # 通过调用group()方法得到匹配的字符串,如果字符串没有匹配,则返回None。 ret = re.search('\d', '1974sadaf93010dsafsa') print(ret) # <_sre.SRE_Match object; span=(0, 1), match='1'> print(ret.group()) # 1 # match:None,同search,不过在字符串开始处进行匹配,完全可以用search+^代替match ret = re.match('\d', '1974sadaf93010dsafsa') print(ret) # <_sre.SRE_Match object; span=(0, 1), match='1'> print(ret.group()) # 1 # split 分割 可按照任意分割符进行分割 ret = re.split('[ab]', 'abcd') # 先按'a'分割得到''和'bcd',在对''和'bcd'分别按'b'分割 print(ret) # ['', '', 'cd'] # sub替换,将数字替换成'H',参数1表示只替换1个,不传默认替换所有 ret = re.sub('\d', 'H', 'aa1bb2cc3dd4', 1) print(ret) # aaHbb2cc3dd4 ret = re.sub('\d', 'H', 'aa1bb2cc3dd4') print(ret) # aaHbbHccHddH # subn替换,将数字替换成'H',返回元组(替换的结果,替换了多少次) ret = re.subn('\d', 'H', 'aa1bb2cc3dd4') print(ret) # ('aaHbbHccHddH', 4) # compile -- 节省代码时间的工具,假如同一个正则表达式要被使用多次,节省了多次解析同一个正则表达式的时间 # 将正则表达式编译成为一个 正则表达式对象,规则要匹配的是3个数字 res = re.compile('\d{3}') ret = res.search('abc123eeee') print(ret) # <_sre.SRE_Match object; span=(3, 6), match='123'> # finditer返回一个存放匹配结果的迭代器 ret = re.finditer('\d', 'ds3sy4784a') print(ret) # <callable_iterator object at 0x0000020A07D6A7B8> print(next(ret).group()) # 查看第一个结果:3 print(next(ret).group()) # 查看第二个结果:4 print([i.group() for i in ret]) # 查看剩余的左右结果:['7', '8', '4']
注意:
findall的优先级查询:
import re ret = re.findall('www.(baidu|oldboy).com', 'www.oldboy.com') print(ret) # ['oldboy'] 这是因为findall会优先把匹配结果组里内容返回,如果想要匹配结果,取消权限即可 # (?:) 取消这个分组的优先显示 ret = re.findall('www.(?:baidu|oldboy).com', 'www.oldboy.com') print(ret) # ['www.oldboy.com']
split的优先级查询
ret=re.split("\d+","eva3egon4yuan") print(ret) #结果 : ['eva', 'egon', 'yuan'] ret=re.split("(\d+)","eva3egon4yuan") print(ret) #结果 : ['eva', '3', 'egon', '4', 'yuan'] #在匹配部分加上()之后所切出的结果是不同的, #没有()的没有保留所匹配的项,但是有()的却能够保留了匹配的项, #这个在某些需要保留匹配部分的使用过程是非常重要的。
命名分组
# 命名分组匹配:(?P<名字>正则表达式) ret = re.search("<(?P<tag_name>\w+)>\w+</(?P=tag_name)>", "<h1>hello</h1>") # 还可以在分组中利用(?P<name>)的形式给分组起名字 # 获取的匹配结果可以直接用group('名字')拿到对应的值 print(ret.group('tag_name')) # 结果 :h1 print(ret.group()) # 结果 :<h1>hello</h1> ret = re.search(r"<(\w+)>\w+</\1>", "<h1>hello</h1>") # 如果不给组起名字,也可以用\序号来找到对应的组,表示要找的内容和前面的组内容一致 # 获取的匹配结果可以直接用group(序号)拿到对应的值 print(ret.group(1)) print(ret.group()) # 结果 :<h1>hello</h1>
shutil模块
shutil模块:高级的文件、文件夹、压缩包 处理模块
shutil.copyfileobj(fsrc, fdst[, length])
import shutil shutil.copyfileobj(open('old.xml','r'), open('new.xml', 'w'))
shutil.copyfile(src, dst)
拷贝文件
shutil.copyfile('f1.log', 'f2.log')
shutil.copymode(src, dst)
仅拷贝权限。内容、组、用户均不变
shutil.copymode('f1.log', 'f2.log')
shutil.copystat(src, dst)
仅拷贝状态的信息,包括:mode bits, atime, mtime, flags
shutil.copystat('f1.log', 'f2.log')
shutil.copy(src, dst)
拷贝文件和权限
import shutil shutil.copy('f1.log', 'f2.log')
shutil.copy2(src, dst)
拷贝文件和状态信息
import shutil shutil.copy2('f1.log', 'f2.log')
shutil.ignore_patterns(*patterns)
shutil.copytree(src, dst, symlinks=False, ignore=None)
递归的去拷贝文件夹
import shutil shutil.copytree('folder1', 'folder2', ignore=shutil.ignore_patterns('*.pyc', 'tmp*')) shutil.copytree('f1', 'f2', symlinks=True, ignore=shutil.ignore_patterns('*.pyc', 'tmp*'))
shutil.rmtree(path[, ignore_errors[, onerror]])
递归的去删除文件
import shutil shutil.rmtree('folder1')
shutil.move(src, dst)
递归的去移动文件,它类似mv命令,其实就是重命名。
import shutil shutil.move('folder1', 'folder3')
shutil.make_archive(base_name, format,...)
创建压缩包并返回文件路径,例如:zip、tar
创建压缩包并返回文件路径,例如:zip、tar
- base_name: 压缩包的文件名,也可以是压缩包的路径。只是文件名时,则保存至当前目录,否则保存至指定路径,
如:www =>保存至当前路径
如:/Users/wupeiqi/www =>保存至/Users/wupeiqi/ - format: 压缩包种类,“zip”, “tar”, “bztar”,“gztar”
- root_dir: 要压缩的文件夹路径(默认当前目录)
- owner: 用户,默认当前用户
- group: 组,默认当前组
- logger: 用于记录日志,通常是logging.Logger对象
#将 /Users/wupeiqi/Downloads/test 下的文件打包放置当前程序目录 import shutil ret = shutil.make_archive("wwwwwwwwww", 'gztar', root_dir='/Users/wupeiqi/Downloads/test') #将 /Users/wupeiqi/Downloads/test 下的文件打包放置 /Users/wupeiqi/目录 import shutil ret = shutil.make_archive("/Users/wupeiqi/wwwwwwwwww", 'gztar', root_dir='/Users/wupeiqi/Downloads/test')
shutil 对压缩包的处理是调用 ZipFile 和 TarFile 两个模块来进行的,详细:
import zipfile # 压缩 z = zipfile.ZipFile('laxi.zip', 'w') z.write('a.log') z.write('data.data') z.close() # 解压 z = zipfile.ZipFile('laxi.zip', 'r') z.extractall() z.close()
import tarfile # 压缩 tar = tarfile.open('your.tar','w') tar.add('/Users/wupeiqi/PycharmProjects/bbs2.log', arcname='bbs2.log') tar.add('/Users/wupeiqi/PycharmProjects/cmdb.log', arcname='cmdb.log') tar.close() # 解压 tar = tarfile.open('your.tar','r') tar.extractall() # 可设置解压地址 tar.close()
logging模块
函数式简单配置
import logging 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名称:用户输出消息。
灵活配置日志级别,日志格式,输出位置:
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')
日志切割
import time import logging from logging import handlers sh = logging.StreamHandler() rh = handlers.RotatingFileHandler('myapp.log', maxBytes=1024,backupCount=5) fh = handlers.TimedRotatingFileHandler(filename='x2.log', when='s', interval=5, encoding='utf-8') logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S %p', handlers=[fh,sh,rh], level=logging.ERROR ) for i in range(1,100000): time.sleep(1) logging.error('KeyboardInterrupt error %s'%str(i))
配置参数:
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 critical message')
logging库提供了多个组件:Logger、Handler、Filter、Formatter。Logger对象提供应用程序可直接使用的接口,Handler发送日志到适当的目的地,Filter提供了过滤日志信息的方法,Formatter指定日志显示格式。另外,可以通过:logger.setLevel(logging.Debug)设置级别,当然,也可以通过
fh.setLevel(logging.Debug)单对文件流设置某个级别。
logger的配置文件
通过logger的对象配置去完成日志的功能,没问题,但是上面这种方式需要创建各种对象,比如logger对象,fileHandler对象,ScreamHandler对象等等,比较麻烦,那么下面给你提供一种字典的方式,创建logger配置文件,这种才是工作中经常使用的实现日志功能的方法,真正的做到 ----- 拿来即用(简单改改)。
""" 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': { #打印到终端的日志 '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', # 日志文件的编码,再也不用担心中文log乱码了 }, }, 'loggers': { #logging.getLogger(__name__)拿到的logger配置 '': { 'handlers': ['default', 'console'], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕 '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': { 'l1': { 'handlers': ['default', 'console'], # 'level': 'DEBUG', 'propagate': True, # 向上(更高level的logger)传递 }, 'l2: { 'handlers': ['default', 'console' ], 'level': 'DEBUG', 'propagate': False, # 向上(更高level的logger)传递 }, 'l3': { 'handlers': ['default', 'console'], # 'level': 'DEBUG', 'propagate': True, # 向上(更高level的logger)传递 }, } #我们的解决方式是,定义一个空的key 'loggers': { '': { 'handlers': ['default', 'console'], 'level': 'DEBUG', 'propagate': True, }, } 这样我们再取logger对象时 logging.getLogger(__name__),不同的文件__name__不同,这保证了打印日志时标识信息不同,但是拿着该名字去loggers里找key名时却发现找不到,于是默认使用key=''的配置