本篇是拆解这篇【python︱函数、for、if、name、迭代器、防范报错、类定义、装饰器、argparse模块、yield 】 将报错 + logger提示拿出来
文章目录
- 1、防范报错
- 1.1 assert 断言
- 2 try...except...
- 报错并提示异常信息
- 优雅的异常报错:suppress
- 3、报错日志记录:Logger.exception
- 3.1 logger - basicConfig 常规存储
- 3.2 常规:logging + handlers + TimedRotatingFileHandler
- 延伸错误信息一起载入log:
- 延申错误二:logger无法显示中文
- 3.3 用.yaml文件配置Log
- 4、with...as...
- 5 主动发出报错raise
- 6 Logger好用的新库:loguru
- 6.1 文件保存
- 6.2 文件保存与删除
- 6.3 常规用法 + 异常函数追踪 - 非常给力!!
1、防范报错
1.1 assert 断言
Python的assert是用来检查一个条件,如果它为真,就不做任何事。如果它为假,则会抛出AssertError并且包含错误信息。例如:
py> x = 23
py> assert x > 0, "x is not zero or negative"
py> assert x%2 == 0, "x is not an even number"
Traceback (most recent call last):
File "", line 1, in
AssertionError: x is not an even number
类似R中的stopifnot
参考博客:Python 中何时使用断言?
.
2 try…except…
两种将报错异常展示的方式:
# 第一种
try:
0/0
except Exception as e:
print (e)
>>> division by zero
# 第二种,更加全面
import traceback
import sys
try:
0/0
except:
traceback.print_exc()
>>>
Traceback (most recent call last):
File "<ipython-input-4-17cf35f30609>", line 4, in <module>
0/0
ZeroDivisionError: division by zero
try:
f = open('xxx')
except:
print 'fail to open'
exit(-1)
如果try中open不出来,那么就except返回相应的内容“fail to open”
try:
<语句> #运行别的代码
except <名字>:
<语句> #如果在try部份引发了'name'异常
except <名字>,<数据>:
<语句> #如果引发了'name'异常,获得附加的数据
else:
<语句> #如果没有异常发生
参考
看一个案例:
try:
print('I am sure no exception is going to occur!')
except Exception:
print('exception')
else:
# 这里的代码只会在try语句里没有触发异常时运行,
# 但是这里的异常将 *不会* 被捕获
print('This would only run if no exception occurs. And an error here '
'would NOT be caught.')
finally:
print('This would be printed in every case.')
# Output: I am sure no exception is going to occur!
# This would only run if no exception occurs.
# This would be printed in every case.
报错并提示异常信息
来源:Python中获取异常(Exception)信息
1、str(e)
返回字符串类型,只给出异常信息,不包括异常信息的类型,如1/0的异常信息
‘integer division or modulo by zero’
2、repr(e)
给出较全的异常信息,包括异常信息的类型,如1/0的异常信息
“ZeroDivisionError(‘integer division or modulo by zero’,)”
3、e.message
获得的信息同str(e)
4、采用traceback模块
需要导入traceback模块,此时获取的信息最全,与python命令行运行程序出现错误信息一致。使用traceback.print_exc()打印异常信息到标准错误,就像没有获取一样,或者使用traceback.format_exc()将同样的输出获取为字符串。你可以向这些函数传递各种各样的参数来限制输出,或者重新打印到像文件类型的对象。
try:
1/0
except Exception, e:
print 'str(Exception):\t', str(Exception)
print 'str(e):\t\t', str(e)
print 'repr(e):\t', repr(e)
print 'e.message:\t', e.message
print 'traceback.print_exc():'; traceback.print_exc()
print 'traceback.format_exc():\n%s' % traceback.format_exc()
或者:
def this_fails():
x = 1/0
try:
this_fails()
except :
print('Handling run-time error:')
raise
print(1)
优雅的异常报错:suppress
但是常碰到的情形是这样的:
• 我们知道这个异常有可能发生
• 我们不关心这个异常,如果发生了,什么也不用处理,直接忽略就好
如果要处理这种情形的异常,那么不必使用 try-except,Python 内置的 contextlib 库提供了一个函数,叫 suppress,是处理这种异常更优雅的方式,Pythonista 一定要尝试使用。
# 单个非0报错
from contextlib import suppress
nums = [3,0,3,0,3]
result = 0
for num in nums:
with suppress(ZeroDivisionError):
result += 1/num
print(result) # 1.0
# 免疫所有报错
for num in nums:
with suppress(Exception):
result += 1/num
# 别这么写
result = 0
nums = [3,0,3,0,3]
with suppress(Exception):
for num in nums:
result += 1/num
print(result) # 0.3333333333333333
3、报错日志记录:Logger.exception
以ERROR级别记录日志消息,异常跟踪信息将被自动添加到日志消息里。Logger.exception通过用在异常处理块中,如:
来源:Python模块学习:logging 日志记录
import logging
logging.basicConfig(filename = os.path.join(os.getcwd(), 'log.txt'), level = logging.DEBUG)
log = logging.getLogger('root')
try:
a===b
except:
log.exception('exception') #异常信息被自动添加到日志消息中
就会在指定目录里面,载入到Log.txt之中
来看看在Log中显示如下:
ERROR:root:exception
Traceback (most recent call last):
File "test.py", line 12, in <module>
a==b
NameError: name 'a' is not defined
所以,在 log.exception()可以简单定义一些关键词来帮助定位问题所在。
3.1 logger - basicConfig 常规存储
来源:python logging模块打印log到指定文件
import logging
import unittest
class lgtest(unittest.TestCase):
logging.basicConfig(filename='../LOG/'+__name__+'.log',format='[%(asctime)s-%(filename)s-%(levelname)s:%(message)s]', level = logging.DEBUG,filemode='a',datefmt='%Y-%m-%d%I:%M:%S %p')
def test(self):
logging.error("这是一条error信息的打印")
logging.info("这是一条info信息的打印")
logging.warning("这是一条warn信息的打印")
logging.debug("这是一条debug信息的打印")
if __name__=='__main__':
unittest.main()
- Filename:指定路径的文件。这里使用了+—name—+是将log命名为当前py的文件名
- Format:设置log的显示格式(即在文档中看到的格式)。分别是时间+当前文件名+log输出级别+输出的信息
- Level:输出的log级别,优先级比设置的级别低的将不会被输出保存到log文档中
- Filemode: log打开模式
- a:代表每次运行程序都继续写log。即不覆盖之前保存的log信息。
- w:代表每次运行程序都重新写log。即覆盖之前保存的log信息
3.2 常规:logging + handlers + TimedRotatingFileHandler
参考:Python + logging 输出到屏幕,将log日志写入文件
参考:python中logging模块下篇
# 信息写入log
import logging
from logging import handlers
class Logger(object):
level_relations = {
'debug':logging.DEBUG,
'info':logging.INFO,
'warning':logging.WARNING,
'error':logging.ERROR,
'crit':logging.CRITICAL
}#日志级别关系映射
def __init__(self,filename,level='info',when='D',backCount=15,\
fmt='%(asctime)s - %(levelname)s: %(message)s'):
self.logger = logging.getLogger(filename) # 创建log文件
format_str = logging.Formatter(fmt)#设置日志格式
self.logger.setLevel(self.level_relations.get(level))#设置日志级别
if not self.logger.handlers:
sh = logging.StreamHandler() # 初始化 1 ,往屏幕上输出
sh.setFormatter(format_str) # 设置屏幕上显示的格式
th = handlers.TimedRotatingFileHandler(filename=filename,when=when,\
backupCount=backCount,encoding='utf-8')
th.setFormatter(format_str)#设置文件里写入的格式
self.logger.addHandler(sh) #把对象加到logger里
self.logger.addHandler(th)
if __name__ == '__main__':
log = Logger('all.log',level='debug')
log.logger.debug('debug')
log.logger.info('info')
log.logger.warning('警告')
log.logger.error('报错'', exc_info=True)
log.logger.critical('严重')
Logger('error.log', level='error').logger.error('error')
屏幕上的结果如下:
2018-03-13 21:06:46,092 - DEBUG: debug
2018-03-13 21:06:46,092 - INFO: info
2018-03-13 21:06:46,092- WARNING: 警告
2018-03-13 21:06:46,099 - ERROR: 报错
2018-03-13 21:06:46,099- CRITICAL: 严重
2018-03-13 21:06:46,100 - ERROR: error
由于when=D,新生成的文件名上会带上时间,如下所示。
其中:
-
level_relations ,代表日志级别,默认等级是WARNING,这意味着仅仅这个等级及以上的才会反馈信息,除非logging模块被用来做其它事情。
- 日志级别: debug < info < warning < error < critical
-
TimedRotatingFileHandler - 按照时间自动分割日志文件
-
interval是时间间隔
-
when是间隔的时间单位,单位有以下几种:
- S 秒
- M 分
- H 小时、
- D 天、
- W 每星期(interval==0时代表星期一)
- midnight 每天凌晨
-
backupCount 是保留日志个数。默认的0是不会自动删除掉日志。若设10,则在文件的创建过程中库会判断是否有超过这个10,若超过,则会从最先创建的开始删除。
-
exc_info,True,代表把错误信息也保存下来,默认是False,不保存
同时,
你会发现如果不设置if not self.logger.handlers:
,那么会出现,重复写日志问题。问题出在,他会不断往log.logger.handlers
添加handlers,上限是三个,就会出现:
第一条记录写一次,第二条记录写两次,第三条记录写三次。
(解决方案来自:python logging 重复写日志问题)
原理在于,如果self.logger.handlers
已经有类了,那么就不要额外添加了。
# 正常的self.logger.handlers长什么样子:
[<logging.StreamHandler at 0x12eba1e3128>,
<logging.handlers.TimedRotatingFileHandler at 0x12eba1e3160>]
# 不正常的self.logger.handlers长什么样子:
[<logging.StreamHandler at 0x12eba1e3128>,
<logging.handlers.TimedRotatingFileHandler at 0x12eba1e3160>,
[<logging.StreamHandler at 0x12eba1e3128>,
<logging.handlers.TimedRotatingFileHandler at 0x12eba1e3160>,]
延伸错误信息一起载入log:
import logging
logging.basicConfig(level=logging.INFO,
format='%(asctime)s %(message)s',
datefmt='%a, %d %b %Y %H:%M:%S +0000',
filename='my.log')
logging.info('this is a info')
try:
do
except Exception:
logging.error('There are something wrong', exc_info=True)
logging.info('continue')
# 报错信息暴露
Sun, 01 Jul 2018 21:10:53 +0000 There are something wrong
Traceback (most recent call last):
File "learn.py", line 9, in <module>
do
NameError: name 'do' is not defined
Sun, 01 Jul 2018 21:10:53 +0000 continue
延申错误二:logger无法显示中文
stream.write(msg)
UnicodeEncodeError: 'ascii' codec can't encode character '\ufffd' in position 363: ordinal not in range(128)
参考:https://github.com/EasyEngine/easyengine/issues/843
解决,其实是系统缺少UTF-8组件,需要载入:
This worked for me:
sudo locale-gen en_US.UTF-8
export LANG=en_US.UTF-8
From: http://askubuntu.com/questions/393638/unicodedecodeerror-ascii-codec-cant-decode-byte-0x-in-position-ordinal-n
3.3 用.yaml文件配置Log
python中logging模块下篇
我们不仅可以通过python代码进行logging配置,而且可以通过写一个yaml文件进行配置,每次需要用logging时只要调用这个文件就配置完成。
config.yaml文件内容如下
version: 1
formatters:
simple:
format: "%(message)s"
more:
format: "%(asctime)s - %(levelname)s - %(message)s"
handlers:
console:
class : logging.StreamHandler
formatter: simple
level: INFO
stream: ext://sys.stdout
file:
class: logging.FileHandler
formatter: more
level: DEBUG
filename: debug.log
loggers:
mainlogger:
level: DEBUG
handlers: [console, file]
root:
level: DEBUG
handlers: [console]
main.py文件中编写代码如下
import logging
import logging.config
import yaml
import a
with open('config.yaml', 'r', encoding='utf-8') as f:
config = yaml.load(f)
logging.config.dictConfig(config)
logging.info('main file log')
a.run()
4、with…as…
那么with和as也是一种防止报错的防范方式,
当python执行这一句时,会调用__enter__函数,然后把该函数return的值传给as后指定的变量。
之后,python会执行下面do something的语句块。最后不论在该语句块出现了什么异常,都会在离开时执行__exit__。
ith open("x.txt") as f:
data = f.read()
with open("x.txt") as f1, open('xxx.txt') as f2:
do something with f1,f2
那么try和with也可以合起:
try:
with open( "a.txt" ) as f :
do something
except xxxError:
do something about exception
延伸:报错提示如何写ValueError()
if pretrained_model not in self._models:
raise ValueError(
'The n_class needs to be supplied as an argument.')
5 主动发出报错raise
参考url:https://blog.csdn.net/skullfang/article/details/78820541
raise RuntimeError('testError')
6 Logger好用的新库:loguru
6.1 文件保存
#!pip install loguru
使用起来:
from loguru import logger
import sys
# 文件保存
# logger.add("log/file.log", rotation="12:00") # 每天中午12点,自动完成归档,并启用新日志文件
# logger.add('runtime_{time}.log', rotation="500 MB") # 设置超过 500 MB 新创建一个 log 文件
# logger.add('runtime_{time}.log', rotation='1 week') # 设置每隔一个周新创建一个 log 文件
# logger.add('runtime_{time}.log', compression='zip') # 可以配置日志文件的压缩格式,这样可以更加节省存储空间,比如设置使用 zip 文件格式保存
logger.add('log/runtime_{time}.log', rotation='00:00') # 每天0点,自动完成归档,并启用新日志文件
logger.add(sys.stdout, colorize=True) # 将日志输出到控制台,并添加颜色
6.2 文件保存与删除
# 文件保存与删除
logger.remove(log_file) # 删除
logger.add('runtime_{time}.log', retention='15 days') # 可以设置日志的最长保留时间,比如设置日志文件最长保留 15 天
logger.add('runtime_{time}.log', retention=10) # 设置日志文件最多保留 10 个
# 文件定时删除
# 也可以是一个 datetime.timedelta 对象,比如设置日志文件最多保留 5 个小时
import datetime
from loguru import logger
logger.add('runtime_{time}.log', retention=datetime.timedelta(hours=5))
6.3 常规用法 + 异常函数追踪 - 非常给力!!
# 常规用法
logger.info("This is log info!")
logger.warning("This is log warn!")
logger.error("This is log error!")
logger.debug("This is log debug!")
# 异常函数追踪 - 非常给力!!
from loguru import logger
@logger.catch
def my_function(x, y, z):
# An error? It's caught anyway!
return 1 / (x + y + z)
my_function(0, 0, 0)
参考文献:
Loguru:Python 日志终极解决方案
loguru:python开箱即用的日志库