1、logging 日志模块
默认情况下Python的logging模块将日志打印到了标准输出中,且只显示了大于等于WARNING级别的日志,这说明默认的日志级别设置为WARNING(日志级别等级CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET),默认的日志格式为日志级别:Logger名称:用户输出消息。
import logging
logging.debug('debug message') logging.info('info message') logging.warning('warning message') logging.error('error message') logging.critical('critical message') 默认只打印:waring、error、critical WARNING:root:warning message1 ERROR:root:error message1 CRITICAL:root:critical message1
在logging.basicConfig()函数中可通过具体参数来更改logging模块默认行为,可用参数有
filename:用指定的文件名创建FiledHandler(后边会具体讲解handler的概念),这样日志会被存储在指定的文件中。
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='test.log', filemode='w')
# level:更改日志的默认等级为DEBUG # format:定义日志的格式,可以自定义 # asctime 是一个变量,下面的 datefmt 决定变量的内容:Mon, 13 Nov 2017 10:36:49 # filename 运行程序的名字:logging 模块.py # lineno 表示程序里面的第几行记录日志:[line:23] # levelname 级别名字:warning、error、critical # message 记录的内容:message1 结果: #Mon, 13 Nov 2017 10:36:49 logging 模块.py[line:23] DEBUG debug message1 # datefmt:日志里面的时间是什么格式:'%a, %d %b %Y %H:%M:%S' # filename:表示日志记录到哪个文件里面:'test.log' # filemode 文件模式:w、a # stream:用指定的stream创建StreamHandler。可以指定输出到sys.stderr,sys.stdout或者文件(f=open('test.log','w')),默认为sys.stderr。若同时列出了filename和stream两个参数,则stream参数会被忽略。 # 注意:如果不指定 filename 和 filemode 的话,会输出到屏幕上 # 其优势就是简单易操作,但缺点很多,比如无法屏幕和文件一起输出等,所以才有了第二种方式:logge
%(name)s Logger的名字 %(levelno)s 数字形式的日志级别 %(levelname)s 文本形式的日志级别 %(pathname)s 调用日志输出函数的模块的完整路径名,可能没有 %(filename)s 调用日志输出函数的模块的文件名 %(module)s 调用日志输出函数的模块名 %(funcName)s 调用日志输出函数的函数名 %(lineno)d 调用日志输出函数的语句所在的代码行 %(created)f 当前时间,用UNIX标准的表示时间的浮点数表示,用 datefmt 即可 %(relativeCreated)d 输出日志信息时的,自Logger创建以来的毫秒数 %(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒 %(thread)d 线程ID。可能没有 %(threadName)s 线程名。可能没有 %(process)d 进程ID。可能没有 %(message)s用户输出的消息
CRITICAL = 50 #FATAL = CRITICAL ERROR = 40 WARNING = 30 #WARN = WARNING 默认级别 INFO = 20 DEBUG = 10 NOTSET = 0 #不设置
应用 logging 模块正确姿势
import logging logger = logging.getLogger() # 实例化一个 logger 对象 logger.setLevel(logging.DEBUG) # 设定 logger 的输出等级,全局级别 # 创建一个handler 对象(文件输出流对象),用于写入日志文件 fh = logging.FileHandler('test1.log') # 文件要指定一个路径 fh.setLevel(logging.WARNING) # 给不同的 handler 对象设置不同的日志级别 fh = handlers.RotatingFileHandler( "chat.log",maxBytes=10,backupCount=3) # 按文件大小切割日志;格式:文件名,文件字节大小,保留的文件个数 比如日志文件是chat.log。当chat.log达到指定的大小之后,自动把文件改名为chat.log.1。不过,如果chat.log.1已经存在,会先把chat.log.1重命名为chat.log.2。。。最后重新创建 chat.log.1,1为最新 fh = handlers.TimedRotatingFileHandler( "web.log",when="S",interval=5,backupCount=3) # 按时间切割日志;格式:文件名,时间类型,间隔多少时间,文件保留个数;新的文件是当前时间。 S 秒 M 分 H 小时 D 天 W 每星期(interval==0时代表星期一) midnight 每天凌晨 # 再创建一个handler(屏幕输出流对象),用于输出到屏幕 ch = logging.StreamHandler() ch.setLevel(logging.DEBUG) # 给不同的 handler 对象设置不同的日志级别 # 创建一个格式化对象 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') # fh 和 ch 把格式化对象 formatter 的功能吸过来 fh.setFormatter(formatter) # 文件输出需要一个格式,fh 对象调用 setformatter 方法指定格式 ch.setFormatter(formatter) # 屏幕输出需要一个格式,ch 对象调用 setformatter 方法指定格式 logger.addHandler(fh) # logger对象把 fh 和 ch 对象里有的功能都吸过来,同时有他们的功能 logger.addHandler(ch) # logger 决定使用哪个对象作为输出,这里同时使用两个,文件输出和屏幕输出 # logger 执行日志输出 # 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') import os,sys with open('i.cfg') as r: ex=os.path.isfile('i1.cfg') # 判断文件是否存在 print(ex) flag=os.path.getsize('i.cfg') # 判断文件是否为空 print(flag)
用Python的logging模块记录日志时,遇到了重复记录日志的问题,第一条记录写一次,第二条记录写两次,第三条记录写三次。。。很头疼,这样记日志可不行。网上搜索到了原因与解决方案: 原因:没有移除handler 解决:在日志记录完之后removeHandler 修改前示例代码: import logging def log(message): logger = logging.getLogger('testlog') streamhandler = logging.StreamHandler() streamhandler.setLevel(logging.ERROR) formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s') streamhandler.setFormatter(formatter) logger.addHandler(streamhandler) logger.error(message) if __name__ == '__main__': log('hi') log('hi too') log('hi three') 修改前输出结果: 2016-07-08 09:17:29,740 - ERROR - testlog - hi 2016-07-08 09:17:29,740 - ERROR - testlog - hi too 2016-07-08 09:17:29,740 - ERROR - testlog - hi too 2016-07-08 09:17:29,740 - ERROR - testlog - hi three 2016-07-08 09:17:29,740 - ERROR - testlog - hi three 2016-07-08 09:17:29,740 - ERROR - testlog - hi three 修改后示例代码: import logging def log(message): logger = logging.getLogger('testlog') streamhandler = logging.StreamHandler() streamhandler.setLevel(logging.ERROR) formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s') streamhandler.setFormatter(formatter) logger.addHandler(streamhandler) logger.error(message) # 添加下面一句,在记录日志之后移除句柄 logger.removeHandler(streamhandler) if __name__ == '__main__': log('hi') log('hi too') log('hi three') 修改后输出结果: 2016-07-08 09:32:28,206 - ERROR - testlog - hi 2016-07-08 09:32:28,206 - ERROR - testlog - hi too 2016-07-08 09:32:28,206 - ERROR - testlog - hi three 深度解析: Google之后,大概搞明白了,就是你第二次调用log的时候,根据getLogger(name)里的name获取同一个logger,而这个logger里已经有了第一次你添加的handler,第二次调用又添加了一个handler,所以,这个logger里有了两个同样的handler,以此类推,调用几次就会有几个handler。。 所以这里有以下几个解决办法: 每次创建不同name的logger,每次都是新logger,不会有添加多个handler的问题。(ps:这个办法太笨,不过我之前就是这么干的。。) 像上面一样每次记录完日志之后,调用removeHandler()把这个logger里的handler移除掉。 在log方法里做判断,如果这个logger已有handler,则不再添加handler。 与方法2一样,不过把用pop把logger的handler列表中的handler移除。 下面是方法3与方法4的代码示例: 方法3: import logging def log(message): logger = logging.getLogger('testlog') # 这里进行判断,如果logger.handlers列表为空,则添加,否则,直接去写日志 if not logger.handlers: streamhandler = logging.StreamHandler() streamhandler.setLevel(logging.ERROR) formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s') streamhandler.setFormatter(formatter) logger.addHandler(streamhandler) logger.error(message) if __name__ == '__main__': log('hi') log('hi too') log('hi three') 方法4: import logging def log(message): logger = logging.getLogger('testlog') streamhandler = logging.StreamHandler() streamhandler.setLevel(logging.ERROR) formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s') streamhandler.setFormatter(formatter) logger.addHandler(streamhandler) logger.error(message) # 用pop方法把logger.handlers列表中的handler移除,注意如果你add了多个handler,这里需多次pop,或者可以直接为handlers列表赋空值 logger.handlers.pop() # logger.handler = [] if __name__ == '__main__': log('hi') log('hi too') log('hi three')
2、configparser 模块
该模块适用于配置文件的格式与windows ini文件类似,可以包含一个或多个节(section),每个节可以有多个参数(键=值)。
import configparser config = configparser.ConfigParser() # 生成一个文件配置的操作句柄,可以对文件 read,write
config['DEFAULT'] = {'ServerAliveInterval': '45', # 往配置文件里添加内容,以字典的形式添加 'Compression': 'yes', 'CompressionLevel': '9'} config['bitbucket.org'] = {'User': 'hg'} config['topsecret.server.com'] = {'Host Port': '50022', 'ForwardX11': 'no'} config['DEFAULT']['ForwardX11'] = 'yes' with open('example.ini', 'w', encoding='utf8') as f: # configfile.write(config) config.write(f) # 调用 config的 write 方法,把 config 对象写到文件里面去
config.read('example.ini') # 需要读文件,就先关联 print(config.sections()) # 读取默认下有几个模块,列表形式,不包含 default 模块 # ['bitbucket.org', 'topsecret.server.com'] print(config.defaults()) # 默认的模块要单独拿出来,打印键值对 ''' OrderedDict([('serveraliveinterval', '45'), ('compression', 'yes'), ('compressionlevel', '9'), ('forwardx11', 'yes')]) ''' print(config.default_section) # 只拿 default 这个模块名字符串 DEFAULT print(config['bitbucket.org']) # 模块对象 <Section: bitbucket.org> print(config['bitbucket.org'].keys()) # bitbucket.org模块键的对象 KeysView(<Section: bitbucket.org>) print(config['bitbucket.org'].values()) # bitbucket.org模块值的对象 ValuesView(<Section: bitbucket.org>) print(config['bitbucket.org'].items()) # bitbucket.org模块键值对的对象 ItemsView(<Section: bitbucket.org>),需要用了两个变量接受 print(config.items('bitbucket.org')) # 打印当前模块的所有键值对 [('serveraliveinterval', '45'), ('compression', 'yes'), ('compressionlevel', '9'), ('forwardx11', 'yes'), ('user', 'alex')] print(config['bitbucket.org']['User']) # 取某个模块下的某个键的值:hg for key in config['bitbucket.org']: # for 循环里面的键 print(key) # 默认default的模块的键值都会赋给下面的单独模块,打印单独的模块下的键值,都会包含默认default模块下的键值 ''' user # bitbucket.org 模块的键 serveraliveinterval # default 的键 compression #default 的键 compressionlevel # default 的键 forwardx11 # default 的键 ''' print(config.options('bitbucket.org')) # 同 for 循环结果,以列表形式 #['user', 'serveraliveinterval', 'compression', 'compressionlevel', 'forwardx11'] print('user' in config['bitbucket.org']) # 判断某个键是否在模块里面,True
config.read('example.ini') #需要删除文件内容,就先关联 config.remove_section('topsecret.server.com') # 删除模块 print(config.has_section('topsecret.server.com')) # 判断有没有这个模块,返回 true、false config.remove_option('bitbucket.org','User') #删除某个 session 下的某个键 config.write(open('example.ini','w')) #无法直接修改文件内容,直接重写一遍一遍再覆盖源文件
config.read('example.ini') #需要改写文件内容,就先关联 config.set('bitbucket.org', 'User', 'alex') # 改变键值,格式:session模块,键,新值 config.write(open('example.ini', 'w')) # 无法直接修改文件内容,这里是直接重写一遍再覆盖源文件
config.read('example.ini') #需要增加文件内容,就先关联 config['baidu.org'] = {'jd': 'lqd'} # 方式1,这种是对整个模块进行重新赋值,会删掉原有 config.add_section('alexsb') # 方式2,先增加模块名 config.set('alexsb', 'k2', 'v2') # 对模块的某个键进行赋值,键不存在会新建一个 config.write(open('example.ini', 'w')) # 写入文件方式1 with open('example.ini', 'w', encoding='utf8') as f: # 写入文件方式2 config.write(f)
3、json、pickle 序列化模块
什么是序列化?
序列化是指把内存里的数据类型转变成字符串,以使其能存储到硬盘或通过网络传输到远程,因为硬盘或网络传输时只能接受bytes,在Python中叫pickling
在其他语言中也被称之为serialization,marshalling,flattening等等,都是一个意思。
反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。
json
如果我们要在不同的编程语言之间传递对象,就必须把对象序列化为标准格式,比如XML,但更好的方法是序列化为JSON,因为JSON表示出来就是一个字符串,可以被所有语言读取,也可以方便地存储到磁盘或者通过网络传输。JSON不仅是标准格式,并且比XML更快,而且可以直接在Web页面中读取,非常方便。
JSON表示的对象就是标准的JavaScript语言的对象,JSON和Python内置的数据类型对应如下:
# json:dump 方法 d={'name':'egon'} with open('new1','w') as f: json.dump(d,f) # dump(需要序列化的对象,文件对象),1:转成json字符串 2:将json字符串写入 f 里 # json:dumps 方法 with open('new2','w') as f1: data=json.dumps(d) # 先用 dumps 把对象转换成 json 字符串,进行序列化 f1.write(data) # 再把序列化后的内容写进文件 # json:loads 方法 with open('new1') as r: d=r.read() data=json.loads(d) # loads 接的是文件内容 print(data['name']) # json:load 方法 with open('new2') as r1: data=json.load(r1) # load 接的是文件对象 print(data['name']) # json load 的时候,不管你是以什么样的形式传过来的,只要符合 json 语法格式就给你 load # new3 手动新建的内容,不经过 dump:{"name1":"egon1"} 必须是双引号,单引号不符合 json 语法格式 with open('new3') as r2: data=json.load(r2) print(data['name1']) # 不用dump数据,只要文件内容格式符合json ,就可以 load 出来 i=10 s='hello' t=(1,4,6) l=[3,5,7] d={'name':"yuan"} json_str1=json.dumps(i) json_str2=json.dumps(s) json_str3=json.dumps(t) json_str4=json.dumps(l) json_str5=json.dumps(d) # 屏幕输出的时候,两边单引号隐藏 print(json_str1) #'10' print(json_str2) #'"hello"' print(json_str3) #'[1, 4, 6]' print(json_str4) #'[3, 5, 7]' print(json_str5) #'{"name": "yuan"}'
import json #dct="{'1':111}" #json 不认单引号,dump 进去单引号,load 出来还是双引号 #dct=str({"1":111}) #报错,因为生成的数据还是单引号:{'one': 1} dct='{"1":"111"}' print(json.loads(dct)) #conclusion: # 无论数据是怎样创建的,只要满足json格式,就可以json.loads出来,不一定非要dumps的数据才能loads
pickle
pickle 与 json 的区别
相同点:
都是通过 dump、dumps、load、loads 数据转换
不同点:
json 可以直接看文件内容
pickle 不能直接看文内容,因为存进去的是以pickle的数据类型,字节格式存进去的
json 不支持时间格式的数据类型,但是可以在多语言之间进行转换,跨语言,体积小
pickle 支持任何数据类型,包括类和函数,但是只能在 python 之间流通,存储数据占空间大
读写方式不一样:
json 是:r、w、a
pickle 是:rb、wb、ab
#时间格式 import datetime import json import pickle t=datetime.datetime.now() d={"data":t} json.dump(d,open("new4","w")) # 直接报错,不是 json 支持的格式 d1={"name":"alvin"} # pickle 可以写,但是直接看文件,是乱码,字节格式 s=pickle.dumps(d) s1=pickle.dumps(d1) f=open('new5',"wb") f.write(s) f.write(s1) f.close() f=open("new5","rb") # 读取数据 data=pickle.loads(f.read()) print(data)
.