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>  
posted @ 2020-07-06 16:44  编程驴子  阅读(409)  评论(0编辑  收藏  举报