python学习笔记26:错误/异常、调试logging、测试
目录
1. 错误
1.1. 处理错误:try..except
关键字 | 作用 | 说明 |
---|---|---|
try | 用于运行可能出错的代码。 | |
except | 如果try代码执行出错,则跳到except语句,处理错误。 | except语句可以有多个分支,分别代表不同的Error类型。 |
else | 可选。else是except语句的分支,在没发生except语句描述的错误时,执行else分支。 | |
finally | 可选。如果最后有finally语句块,则执行finally语句块。 | 无论是否有错误发生,finally都会执行。 |
执行顺序:try->except->else->finally->完成。
python的错误也是class,继承自BaseException,每个except不但捕获该类错误,还把子类也捕获了,比如ValueError会捕获UnicodeError。
import sys
try:
print(‘try…’)
a = sys.argv[1]
r = 10 / int(a)
print(‘result’, r)
except IndexError as e: # e中存储的是错误信息:list index out of range
print(‘IndexError: ’, e)
except ValueError as e:
print(‘ValueError:’, e)
except ZeroDivisionError as e:
print(‘ZeroDivisionError:’, e)
else:
print(‘No Error Occus’)
finally:
print(‘finally…’)
print(‘Done’)
测试:
$ xx.py
try...
IndexError: list index out of range # except IndexError被执行
finally # finally语句总是被执行
Done
$ xx.py 0
try...
ZeroDivisionError: division by zero # except ZeroDivisionError被执行
finally # finally语句总是被执行
Done
$ xx.py a
try...
ValueError: invalid literal for int() with base 10: ‘a’ # 触发ValueError
finally # finally语句总是被执行
Done
$ xx.py 1
try...
result: 10.0
No error occus # 如果没有触发Error,则else语句被执行
finally # finally语句总是被执行
Done
捕获可以跨层次调用:
>>> def foo(x): return 10/int(x)
>>> def bar(x): return foo(x)*2
>>> def main():
... try: bar(‘0’)
... except Exception as e: print(f‘Error: {e}’)
... finally: print(‘finally’)
... print(‘Done’)
...
>>> main() # foo上报给bar,bar上报给main,main可以处理错误
Error: division by zero
finally
Done
如果错误没捕获,会一直上报,最终被Python解释器捕获,然后打印错误,退出程序;
也可以使用logging记录出错信息,让程序出错后不退出。
>>> import logging
>>> def foo(x): return 10/int(x)
>>> def bar(x): return foo(x)*2
>>> def main():
... try: bar(‘0’)
... except Exception as e: logging.exception(e)
... finally: print(‘finally’)
... print(‘Done’)
...
>>> main() # main捕获错误后记录到logging,并继续执行程序
ERROR:root:division by zero
Traceback (most recent call last):
File “xx.py”, line 5 in main
try: bar(‘0’)
File “xx.py”, line 3 in bar
def bar(x): return foo(x)*2
File “xx.py”, line 2 in foo
def foo(x): return 10/int(x)
ZeroDivisionError: division by zero
finally
Done
1.2. 抛出错误:raise
内置函数能抛出错误,我们自己写的函数也可以抛出错误。
可以自己定义错误类型,但不常用;如果可以选择Python已有的内置错误类型,尽量使用内置错误类型。
raise语句,如果没有参数,则把错误原样抛出,如果带参数(另一个错误类型),则把原来的错误转化为指定的类型。
自己定义一个错误类型
>>> class FooError(ValueError): pass
>>> def foo(s):
... n = int(s)
... if n==0:
... raise FooError(f‘invalid value: {s}’) # raise自定义的错误
... return 10/n
...
>>> foo(‘0’)
***
***
__main__.FooError: invalid value: 0
>>> def foo(s):
...
2. 调试
2.1. 使用assert
断言失败则抛出AssertionError;
>>> def foo(s):
... n = int(s)
... assert n!=0, ‘n is zero.’
... return 10/n
...
>>> foo(0)
Traceback (most recent call last):
File “<stdin>”, line 1, in <module>
File “<stdin>”, line 3, in foo
AssertionError: n is zero.
可以通过使用-0选项来关闭assert:python -0 xx.py,这时assert语句相当于pass。
2.2. 使用logging
设置log输出格式:
>>> logging.basicConfig(
... level= logging.DEBUG, #设置打印级别为debug
... format = '%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)-8s: %(message)s',
... )
>>> logging.debug(f'log for debug')
2018-08-13 10:56:44,027 - <stdin>[line:1] – DEBUG: log for debug
>>>
>>> logging.warning(f'log for warning')
2018-08-13 10:57:57,725 - <stdin>[line:1] – WARNING : log for warning
logging的级别:NOTSSET < DEBUG < INFO < WARNING < ERROR < CRITICAL
>>> import logging
>>>
>>> logging.critical(f‘log for critical’) # 打印critical
CRITICAL:root:log for critical
>>>
>>> logging.error(f‘log for error’) # 打印error
ERROR:root:log for error
>>>
>>> logging.warning(f‘log for warning’) # 打印warning
WARNING:root:log for warning
>>>
>>> logging.info(f‘log for info’) # 打印info,默认不打印
>>> logging.debug(f‘log for debug’) # 打印debug,默认不打印
设置打印级别
>>> logging.basicConfig(level=logging.DEBUG) #设置打印级别为debug
>>> logging.info(f'log for info') # 打印info, 正常打印
INFO:root:log for info
>>>
>>> logging.debug(f'log for debug') # 打印debug, 正常打印
DEBUG:root:log for debug
设置log输出到文件和Terminal:
程序:
import logging
#1. 创建一个logger
logger = logging.getLogger()
logger.setLevel(logging.DEBUG) # Log等级总开关
#2. 设置log格式:
formatter=logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)-8s: %(message)s')
#3. 创建一个fh,并添加到logger,用于写log到文件
fh = logging.FileHandler('./try.log', mode='w') # log文件名
fh.setLevel(logging.INFO) # 输出到log file的等级开关
fh.setFormatter(formater) # 使用前面定义的formatter
logger.addHandler(fh)
#4. 创建一个ch,并添加到logger,用于写log到Terminal
ch = logging.StreamHandler()
ch.setLevel(logging.WARNING) # 输出到Terminal的等级开关
ch.setFormatter(formater) # 使用前面定义的formatter
logger.addHandler(ch)
#5. 写log
logging.critical(f'log for critical')
logging.error(f'log for error')
logging.warning(f'log for warning')
logging.info(f'log for info')
logging.debug(f'log for debug')
执行,查打印到Terminal和file的log,
Terminal”
$ xx.py # Termimal设置只打印到Warning级别的log, info和debug级别的没显示
2018-08-13 11:06:47,742 - <xx.py>[line:21] – CRITICAL: log for critical
2018-08-13 11:06:47,743 - <xx.py>[line:22] – ERROR: log for error
2018-08-13 11:06:47,744 - <xx.py>[line:23] – WARNING : log for warning
文件:
$ cat try.log # file设置只打印到INFO级别的log, debug级别的log没有显示
2018-08-13 11:06:47,742 - <xx.py>[line:21] – CRITICAL: log for critical
2018-08-13 11:06:47,743 - <xx.py>[line:22] – ERROR: log for error
2018-08-13 11:06:47,744 - <xx.py>[line:23] – WARNING : log for warning
2018-08-13 11:06:47,743 - <xx.py>[line:22] – INFO: log for info
formatter常用格式:
格式 | 说明 |
---|---|
%(levelno)s | 打印日志级别的数值 |
%(levelname)s | 打印日志级别名称 |
%(pathname)s | 打印当前执行程序的路径,其实就是sys.argv[0] |
%(filename)s | 打印当前执行程序名 |
%(funcName)s | 打印日志的当前函数 |
%(lineno)d | 打印日志的当前行号 |
%(asctime)s | 打印日志的时间 |
%(thread)d | 打印线程ID |
%(threadName)s | 打印线程名称 |
%(process)d | 打印进程ID |
%(message)s | 打印日志信息 |
把异常信息打印到logging:
import logging
import traceback
try:
code for run
except:
logging.error(str(traceback.format_exc()))
2.3. 使用pdb
调试器pdb,可以单步运行程序,或者设置断点;
2.3.1. 调用pdb,单步执行
程序文件test.py如下:
s = '0'
n = int(s)
print(10/n)
执行如下
$ python -m pdb test.py #在命令行调用pdb模块, 对py_test.py进行单步调试
> path/test.py(3808)<module>() #自动显示下一步要执行哪个文件的哪一行
-> s = '0' #自动显示下一步要执行的代码
(Pdb) n #用户输入n, 执行一步代码 s='0'
> path/test.py(3809)<module>() #执行代码, 然后自动显示下一步要执行哪一行
-> n = int(s)
(Pdb) n #用户输入n, 执行一步代码 n=int(s)
> path/test.py(3810)<module>() #执行代码, 然后自动显示下一步要执行哪一行
-> print(10/n)
(Pdb) n #用户输入n, 执行一步代码 print(10/n)
ZeroDivisionError: division by zero #执行代码报错
> path/test.py(3810)<module>() #代码无法执行, 仍然显示这一段代码
-> print(10/n)
(Pdb) p n #p n表示查看变量n的值
0
(Pdb) q #按q退出
2.3.2. 设置断点执行
程序文件如下:
import pdb
pdb.set_trace() # 运行到这会自动暂停
s = '0'
n = int(s)
print(n)
pdb.set_trace() # 在代码调试中按c会执行到下一个set_trace()
print(10/n)
执行如下
$ python test.py #正常执行程序(因为pdb在程序中导入)
> path\test.py(3809)<module>() #程序执行到第一个set_trace()断点
-> s = '0' #显示下一步要执行的代码
(Pdb) c #用户输入c, 执行到下一个set_trace()
0
> path\test.py(3813)<module>() #下一个set_trace()处的代码
-> print(10/n)
(Pdb) n #用户输入n, 执行下一步
ZeroDivisionError: division by zero #执行出错
> path\test.py(3813)<module>() #仍然停留在出错的代码处
-> print(10/n)
(Pdb) c #用户输入c, 执行到下一个set_trace()
Traceback (most recent call last): #python抛出异常
File "test.py", line 3813, in <module>
print(10/n)
ZeroDivisionError: division by zero
$ #python遇到异常后退出到命令行
3. 单元测试
使用unittest做单元测试:
import unittest # 使用unittest模块做测试
class TestStudent(unittest.TestCase): # 测试类从TestCase继承
def setUp(self): # 调用每条测试方法前会调用setUp()方法
print('\nTest Begin setUp...')
def tearDown(self): # 调用每条测试方法后会调用tearDown()方法
print('Test End tearDown...')
def test_80_to_100(self): # 以test开头的方法被认为是测试方法
s0 = Student()
self.assertTrue(s0.grade > 0) # 判断为真
self.assertEqual(s0.grade, 90) # 判断相等
with self.assertRaise(ValueError): # 判断语句会raise ValueError
s0.set_grade(101)
if __name__ = ‘__main__’:
unittest.main() # 运行测试
4. 文档测试
使用doctest做文档测试,提取注释中的代码并执行测试;
编辑文件test.py
def abs_new(n):
'''''
>>> abs_new(1)
1
>>> abs_new(-1) # 人为加入一个错误输出
2
>>> abs_new(0)
0
>>> abs_new('a')
Traceback (most recent call last):
... # 这里内容可以省略,
... # 但Traceback和ValueError的内容要与真正报错内容一样,
... # 也不能加注释
ValueError: must be a int
'''
if isinstance(n, int):
return n if n>0 else -n
else:
raise ValueError('must be a int')
if __name__ == '__main__':
import doctest
doctest.testmod() # 使用doctest测试注释中的代码,
# 如果注释正确, 则什么也不输出.
# 此处abd_new()中人为加入了个错误, 会输出错误
运行该脚本, 由于注释代码中人为加入了错误, 会报告如下信息
E:\myfile\test>python test.py
**********************************************************************
File "test.py", line 5, in __main__.abs_new
Failed example:
abs_new(-1) # 人为加入一个错误输出
Expected:
2
Got:
1
**********************************************************************
1 items had failures:
1 of 4 in __main__.abs_new
***Test Failed*** 1 failures.
E:\myfile\py_test>