Python之异常处理
- 2019 年 11 月 6 日
- 笔记
1
Python中的错误处理
在程序运行的过程中,难免会出现这样那样的错误,有些错误是我们自己程序编写上有问题,也就是程序员听了会砍人的那句话,"哟,写bug呢!",还有一种是无法预测的错误,例如磁盘写满了,又或者从网络抓取数据的时候,网络连接突然崩溃等等。Python中内置了一套异常处理机制,可以帮助我们对这些错误进行处理。他就是try…except…finally的错误处理机制。
首先我们来看一个应用实例:
try: print('try...') r = / print('result:', r) except ZeroDivisionError as e: print('except:', e) finally: print('finally...') print('END')
上面的方法中,当我们认为某些代码可能存在一定的安全隐患的时候,可以使用try来运行这段代码,这样做的好处是,如果这段代码真的存在错误,则后续的代码不会执行,而是会直接跳转至错误处理代码,也就是except模块,执行完except之后,如果有finally语句,则执行finally语句,否则执行完毕,上面的代码很明显,除数为0的算法肯定是错误的。我们把它写成一个函数test(),可以看到输出值如下(代码可以左滑):
>>> def test(a): ... try: ... print('try...') ... r = / a ... print('result:', r) ... except ZeroDivisionError as e: ... print('except:', e) ... finally: ... print('finally...') ... print('END') ... return ... >>> a=test() try... ('result:', ) finally... END >>> b=test() try... ('except:', ZeroDivisionError('integer division or modulo by zero',)) finally... END >>>
我们输入参数0的时候,函数返回了错误码和相应的提示。
在上面的例子中,只定义了一种错误,实际情况中,可能有各种各样的非法输入,这就需要我们制定不同的except,从而对真正的错误原因进行区分:
>>> def test(a): ... try: ... print('try...') ... r = / int(a) ... print('result:', r) ... except ValueError as e: ... print('ValueError:', e) ... except ZeroDivisionError as e: ... print('ZeroDivisionError:', e) ... finally: ... print('finally...') ... print('END') ... >>> a=test('a') try... ('ValueError:', ValueError("invalid literal for int() with base 10: 'a'",)) finally... END >>> b=test() try... ('result:', ) finally... END >>> c=test() try... ('ZeroDivisionError:', ZeroDivisionError('integer division or modulo by zero',)) finally... END >>>
上面的结果可以看出,当我们输入一个字母a的时候,返回的错误结果和输入数字0的结果不同,但是他们都触发了异常捕获。
使用try…except还有另外一个好处,就是可以跨越多层调用,比如函数main()调用bar(),bar()调用foo(),如果foo()函数出错了,此时,只要main函数捕获到了,就可以进行处理:
def foo(s): return / int(s) def bar(s): return foo(s) * def main(): try: foo('0') except Exception as e: print('Error:', e) finally: print('finally...')
也就是说,不需要在每个可能出错的地方去捕获错误,只要在合适的层次去捕获错误就可以了。这样一来,就大大减少了写try...except...finally
的麻烦。
如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,然后程序退出。来看一个错误的例子:
# err.py: def foo(s): return / int(s) def bar(s): return foo(s) * def main(): bar('0') main()
当我们调用这个文件的时候,会出现如下的错误:
$ python err.py Traceback (most recent call last): File "err.py", line , in <module> main() File "err.py", line , in main bar('0') File "err.py", line , in bar return foo(s) * File "err.py", line , in foo return / int(s) ZeroDivisionError: division by zero
层层分析,最后我们可以发现是在foo函数中使用0作为分母,从而出现了错误。这个过程中,我们可以看到函数的调用栈是由外而内的。
2
记录错误,继续执行
当出现错误的时候,如果我们想要继续执行后面的程序,对当前的错误仅仅做一个捕获操作,我们可以使用Python内置的logging模块:
# err_logging.py import logging def foo(s): return / int(s) def bar(s): return foo(s) * def main(): try: bar('0') except Exception as e: logging.exception(e) main() print('继续执行,结果是...')
当我们对上面这段代码进行执行的时候,我们可以看到如下结果:
ERROR:root:integer division or modulo by zero Traceback (most recent call last): File "<stdin>", line , in main File "<stdin>", line , in bar File "<stdin>", line , in foo ZeroDivisionError: integer division or modulo by zero 继续执行,结果是...
函数最终还是执行了print,但是将中途所有的错误都捕获到了。这就是logging的作用,需要注意的是,在使用logging之前,先要对logging模块进行导入。通过配置,logging还可以把错误记录到日志文件中,方便以后排查。
3
抛出错误
在Python中,每一个错误都是一个class,所有的错误类型都继承自BaseException,在使用except的时候需要注意,它不但不获该类型的错误,还把其子类的错误一网打尽。捕获一个错误就是捕获该class的一个实例,Python内置的函数会抛出很多类型的错误,如果我们想自己自定义一个错误,可以使用下面的方法:
# err_raise.py class FooError(ValueError): pass def foo(s): n = int(s) if n==: raise FooError('invalid value: %s' % s) return / n foo('0')
执行上面这段代码,我们可以通过跟踪找到我们自己定义的错误类型:
>>> python err_raise.py Traceback (most recent call last): File "<stdin>", line , in <module> File "<stdin>", line , in foo __main__.FooError: invalid value: >>>
只有在必要的时候才定义我们自己的错误类型。如果可以选择Python已有的内置的错误类型(比如ValueError
,TypeError
),尽量使用Python内置的错误类型。