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内置的错误类型。