引言
我们用什么语言写项目,其实都会出错,错误有很多种,有我们写的代码本身就有问题,这个时候属于编译错误,有的代码运行的时候有问题,这个属于运行时异常,python其实也有一套自己的异常处理机制。今天我们来学习一下。
解决错误中的一种方式:返回错误码
返回错误码其实是程序员排除错误的一种方式,他们往往会有一个错误码的字典,字典里面记录了什么数字对应着什么错误;然后当程序返回什么码的时候,去再对照之前那个字典,就能够知道什么错误了。
但是错误码其实有一个局限性,就是当我们返回的结果如果和错误码混合在一起,那么久不太好区分了。
为了避免这种问题,我们常用try catch finally(java语言中)等方式进行运行时异常的捕获。
当然python也有类似的。
try...except...finally
我们先试用一下
try:
print('try...')
r=10/0
print('result:',r)
except ZeroDivisionError as e:
print('except:',e)
finally:
print('finally...')
print('END')
大家应该能看懂大概意思,我们来看下实际输出结果
当我们认为某些代码可能会出错时,就可以用try来运行这段代码
如果执行出错,则后续代码不会继续执行,而是直接跳转至except语句块
执行完except后,如果有finally语句块,则执行finally语句块
至此,执行完毕
ok,当然,如果没有错误出现,那么也不会执行except代码块,如下如
(注意:如果想得到整数,那么用"//")
从上面的输出结果,可以看到,不论异常有没有finally一直都会执行,就像java中的一样
那么,如果发生了不同种的错误,是不是可以使用多种except去处理呢?
答案是肯定的!
我们直接来看示例
这个例子中,我们用一个 except 来捕获 ValueError ,一个 except 来捕获 ZeroDivisionError
此外,如果没有错误发生的话,还可以在 except 后添加一个 else: 去执行没有该错误的处理
如下面例子所示
需要注意的是,并不是一个 except 对应一个 else ,而是当所有的 except 都没处理的话,才会执行唯一的 else ,下面的写法是错的
错误写法
Python的错误也是类,所以如果想捕获某一大类的错误,直接捕获大类就好了
所有的错误类,都继承自 BaseException类
常见的错误类型和继承关系看这里:
https://docs.python.org/3/library/exceptions.html#exception-hierarchy
使用try...except捕获错误还有一个巨大的好处,就是可以跨越多层调用
请看下方示例:
由此我们发现,有了这个跨越多层调用 except ,大大减少了复杂的 try except finally 代码的使用
调用栈
如果出的错没有处理,那么最终会被 python 解释器捕获,打印一个错误信息,然后程序退出
我们来演示一下
出错的时候,一定要分析错误的调用栈信息,才能定位错误的位置
出了错之后要懂得找打错误的位置,上面的例子错误的位置就在第三行,10/s的位置,找到错误的位置,确定错误的类型,我们就能够解决了
logging类
我们从上面的例子知道了,即使我们不进行异常的捕获,python编译器也会对异常进行处理,我们也能定位到异常的位置,那么还要异常处理干什么?
其实不然,python处理器虽然会处理异常,但是在处理的同时,也将程序终止。
其实我们可以借助logging类去处理,logging类既会想python处理器一样将错误展示出来,也不会终止程序,如下例所示
抛出错误
需要明确的是:
错误是一个 class ,我们捕获的错误其实就是捕获了该 class 的一个实例
所以说,错误并不是凭空出现的,而是有意抛出的
python内置的函数会抛出很多类型的错误,当然我们自己编写的函数也可以抛出错误。
如果要抛出错误,我们可以根据需要,可以定义一个表示错误的 class ,然后选择好继承关系,
最后用 raise 语句抛出一个错误的实例。
比如,现在定义一个错误,让他继承ValueError
class FooError(ValueError):
pass
然后在适当的地方抛出
def foo(s):
if s==0:
raise FooError('Foo Error %s' %s)
return 10/s
这样,当我们调用foo函数时,如果我们传入
0
那么将会抛出FooError的错误,并且打印
“Foo Error 0”
否则的话,则会直接返回 10/s 的返回值
我们来看一下真实的案例演示:
但是,我们只有在必要的时候才会定义我们自己的错误类型,在定义错误类型时,一定要继承对父类,否则会造成麻烦
下面再来看另外一中处理错误的方式:重复抛出错误
由上图,我们可以看出,虽然已经捕获了异常:打印出了FooError!
但是又将之前的异常抛出,这是什么操作呢?
这个操作有的时候很有必要
有的情况下,由于当前函数不知道怎样去处理当前遇到的错误,那么最恰当的方式是往上抛,让顶层调用者去处理。就好比像一个员工遇到了一个棘手的问题不知道怎么处理,那么他最恰当的做法是交给他的上司,如果他的上司也处理不好,那么就会一直往上抛,直到抛给公司的大老板,让大老板去处理
注意用法
raise 如果直接写的话,那么就是把错误直接原封不动的抛给上级
如果raise 后追加别的错误类型的话,其实就完成了把错误转化成另外一个类型额操作了,如
try:
10/s
except ZeroDivisionError as e:
print('ZeroDivisionError')
raise ValueError('ValueError')
当然了,这种转化只要符合逻辑就可以,但是切忌瞎转化。