高级语言包括python一般都内置了一套try…catch…finally的错误处理机制:
>>> try: ... print('try...') ... r = 10 / 0 ... print('result:', r) ... except ZeroDivisionError as e: ... print('except:', e) ... finally: ... print('finally...') ... try... except: division by zero finally...
如果认为某些代码可能会出错,可以用try来运行这段代码;
如果try的代码块出现错误,则try代码省下的代码不会继续执行,而是直接跳转到catch代码块,catch就是错误处理代码块(如果没有错误,则不执行)
如果还有finally代码块,则执行finally代码块。没有则不执行
我们看到代码执行 10 / 0 的时候出现了错误(0不能作为除数),下面测试没有错误的情况
>>> try: ... print('try……') ... r = 10 / 2 ... print('结果:%s' % r) ... except ZeroDivisionError as e: ... print('发生了异常:',e) ... finally: ... print('最后执行……') ... try…… 结果:5.0 最后执行……
如果try代码块可能出现多种错误类型,可以编写多个except代码块来处理;此外,如果没有发生错误,还可以在except代码块后面加上else语句,当没有错误的时候,会自动执行else语句:
>>> try: ... print('开始:') ... r = 10 / int('2') ... print('结果:',r) ... except ValueError as e: ... print('ValueError:',e) ... except ZeroDivisionError as e: ... print('ZeroDivision:',r) ... else: ... print('没有出错!') ... finally: ... print('最后要执行的代码') ... 开始: 结果: 5.0 没有出错! 最后要执行的代码
万物皆对象,python的错误也是class,所有的错误类型都继承自BaseException,各个类型的错误之间可能会存在继承关系,比如UnicodeError是ValueError的子类,如果catch语句中同时出现了这两个错误,且UnicodeError在ValueError的后面处理的,那么永远都捕获不到UnicodeError。
下面是python中内置的常用错误类型继承关系:
BaseException +-- SystemExit +-- KeyboardInterrupt +-- GeneratorExit +-- Exception +-- StopIteration +-- StopAsyncIteration +-- ArithmeticError | +-- FloatingPointError | +-- OverflowError | +-- ZeroDivisionError +-- AssertionError +-- AttributeError +-- BufferError +-- EOFError +-- ImportError | +-- ModuleNotFoundError +-- LookupError | +-- IndexError | +-- KeyError +-- MemoryError +-- NameError | +-- UnboundLocalError +-- OSError | +-- BlockingIOError | +-- ChildProcessError | +-- ConnectionError | | +-- BrokenPipeError | | +-- ConnectionAbortedError | | +-- ConnectionRefusedError | | +-- ConnectionResetError | +-- FileExistsError | +-- FileNotFoundError | +-- InterruptedError | +-- IsADirectoryError | +-- NotADirectoryError | +-- PermissionError | +-- ProcessLookupError | +-- TimeoutError +-- ReferenceError +-- RuntimeError | +-- NotImplementedError | +-- RecursionError +-- SyntaxError | +-- IndentationError | +-- TabError +-- SystemError +-- TypeError +-- ValueError | +-- UnicodeError | +-- UnicodeDecodeError | +-- UnicodeEncodeError | +-- UnicodeTranslateError +-- Warning +-- DeprecationWarning +-- PendingDeprecationWarning +-- RuntimeWarning +-- SyntaxWarning +-- UserWarning +-- FutureWarning +-- ImportWarning +-- UnicodeWarning +-- BytesWarning +-- ResourceWarning
使用try…catch…捕获错误一个好处就是,可以跨层调用,比如main()调用foo(),foo()调用bar(),而错误是在bar中出现的,最后我们只需要在main()中捕获就行:
>>> def foo(s): ... return 10 / int(s) ... >>> def bar(s): ... return foo(s)*2 ... >>> def main(): ... try: ... bar('0') ... except Exception as e: ... print('Error:',e) ... finally: ... print('最终要执行的代码') ... >>> main() Error: division by zero 最终要执行的代码
调用栈
如果没有捕捉错误,该错误就会一直往上抛,最后被python解释器捕获,并打印一条错误消息,然后退出程序。下面新建一个err.py文件:
def foo(s): return 10 / int(s) def bar(s): return foo(s) * 2 def main(): bar('0') main()
执行结果:
PS E:\Python3.6.3\workspace> python err.py Traceback (most recent call last): File "err.py", line 8, in <module> main() File "err.py", line 6, in main bar('0') File "err.py", line 4, in bar return foo(s) * 2 File "err.py", line 2, in foo return 10 / int(s) ZeroDivisionError: division by zero
上面的信息我们可以看到整个错误的函数调用栈:
错误第一行:
Traceback (most recent call last):
告诉我们以下是错误的跟踪信息。
第2~3行:
File "err.py", line 8, in <module> main()
告诉我们err.py执行中,main()出错了,在代码的第8行。
第4~5行:
File "err.py", line 6, in main bar('0')
告诉我们错误原因是第6行代码。
依次往下,最终告诉我们是foo函数出错了:
File "err.py", line 2, in foo return 10 / int(s)
这就是错误的源头,因为控制台打印了错误类型:
ZeroDivisionError: division by zero
这是个ZeroDivisionError,我们分析并不是Int(s)本身定义或者语法有错误,而是int(s)返回值为0,从而找到了源头。
上面说了当我们在程序中不捕获错误的时候,python解释器会在自动打印错误的堆栈,但是程序也会戛然而止。我们可以选择把错误堆栈打印出来,同时程序会继续执行下去。怎么操作呢?python内置的logging模块可以非常清楚的记录错误信息,新建一个err_logging.py文件:
import logging def foo(s): return 10 / int(s) def bar(s): return foo(s)*2 def main(): try: bar('0') except Exception as e: logging.exception(e) main() print('最后执行了……')
执行结果:
ERROR:root:division by zero Traceback (most recent call last): File "err_logging.py", line 8, in main bar('0') File "err_logging.py", line 5, in bar return foo(s)*2 File "err_logging.py", line 3, in foo return 10 / int(s) ZeroDivisionError: division by zero 最后执行了……
同样出错了,但是程序处理完错误信息后会继续执行。
因为错误对象就是class,其实我们自己也可以自定义错误用于抛出。
首先,应该定义一个错误的类,选择继承关系,然后用raise关键字抛出实例,创建一个err_raise.py文件:
class FooError(ValueError): pass def foo(s): n = int(s) if n == 0 : raise FooError('非法的数值:%s' % s) return 10 / n foo('0')
执行后,如果有错误,我们可以追踪到自己定义的错误:
PS E:\Python3.6.3\workspace> python err_raise.py Traceback (most recent call last): File "err_raise.py", line 9, in <module> foo('0') File "err_raise.py", line 6, in foo raise FooError('非法的数值:%s' % s) __main__.FooError: 非法的数值:0
有些时候我们会碰到一些当前代码不适合处理或者不能处理的错误,我可以选择记录下错误之后,在向上抛,这时在except代码块中加入raise语句。raise语句还可以将错误类型转化。