• python学习笔记26:错误/异常、调试、测试


    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 打印日志信息

    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	est.py(3809)<module>()      #程序执行到第一个set_trace()断点  
    -> s = '0'                          #显示下一步要执行的代码  
      
    (Pdb) c                             #用户输入c, 执行到下一个set_trace()  
    0  
    > path	est.py(3813)<module>()      #下一个set_trace()处的代码  
    -> print(10/n)  
      
    (Pdb) n                             #用户输入n, 执行下一步  
    ZeroDivisionError: division by zero #执行出错  
    > path	est.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('
    Test 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	est>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:myfilepy_test>  
    
  • 相关阅读:
    C#|.NET从控制反转(依赖注入)想到事件注入 (非AOP)
    libevent 2.1.3 for VS2008 source code
    如何进行object以及Array(数组)的深复制
    Flash ProgressEvent.bytesTotal为0的原因和解决
    Flash字体嵌入方法总结—(4)进阶篇
    mysql分组取每组前几条记录(排名) 附group by与order by的研究
    Flash游戏优化技巧
    教程:深入理解Flash的沙箱 – Application Domains
    Flash Player 11异步解码Bitmap
    常用公式
  • 原文地址:https://www.cnblogs.com/gaiqingfeng/p/13255557.html
Copyright © 2020-2023  润新知