本文部分内容整理自:阿里云大学 - python学习路线 - 课时42~45
避免程序在运行中,因为一些异常错误(例如一个数除以0、打印未知变量等)而出现闪退、崩溃等立即停止的情况。
因此,针对有可能出错的代码,我们需要实现以下效果:
try:
代码块(可能出现错误的语句)
except:
代码块(出现错误后的处理方式)
else:
代码块(未出现错误后的处理方式,可选)
finally:
代码块(无论是否出现错误都会执行,和'except'至少有一个)
注意:如果 try 语句里有多个异常,except 会在第一个异常发生时进行处理。
一段代码在执行的过程中,异常既可能出现在全局,也可能出现在函数内。当异常出现在函数内,并且我们调用了这个函数时(不调用就不存在异常了),就会发生异常的传播。
print(2/0)
Traceback (most recent call last):
File "D:\code\test.py", line 1, in <module>
print(2/0)
ZeroDivisionError: division by zero
def fun():
print(2/0)
fun()
Traceback (most recent call last):
File "D:\code\test.py", line 3, in <module>
fun()
File "D:\code\test.py", line 2, in fun
print(2/0)
ZeroDivisionError: division by zero
我们可以看到,python会显示两处错误的地方,第一处是调用产生错误的函数时,第二处是函数内出错的地方。
当在函数中出现异常时,如果在函数/类中对异常进行了处理,则异常不会再继续传播。
如果函数中没有对异常进行处理,则异常会继续向函数调用处传播。
如果函数调用处处理了异常,则不再传播,否则异常继续向调用处传播。
直到传递到全局作用域(主模块),如果依然没有处理,则程序终止并显示异常信息。
def fun1():
print(2/0)
def fun2():
fun1()
def fun3():
fun2()
fun3()
这段代码中,异常在 fun1() 中发生,会依次传递到 fun2() ,fun3() ,最后进入全局。按从下往上的顺序产生以下报错。(可以理解为最下面的是最内层的错误)
Traceback (most recent call last):
File "D:\code\test.py", line 10, in <module>
fun3()
File "D:\code\test.py", line 8, in fun3
fun2()
File "D:\code\test.py", line 5, in fun2
fun1()
File "D:\code\test.py", line 2, in fun1
print(2/0)
ZeroDivisionError: division by zero
注:类里的语句会在导入类所在模块时自动运行,所以报错也会提前到导入当前模块时(主模块就是直接运行时)。
当程序运行过程中出现异常以后,所有的异常信息会被保存到一个专门的异常对象中。异常传播,实际上就是把异常对象抛给调用处。
在前面的例子中,ZeroDivisionError 就是一个异常类。
python 中为我们提供了多个异常类,可以在 python 的 documentation 文档中,Library Reference 目录下的 Built-in Exceptions 中查询所有异常类。
异常对象就是异常类的实例。
这里,我们拓展之前 try 语句基本用法里 except 语句的用法。
except : 或 except Exception :
如果 except 后面不跟任何内容,此时它会捕获到所有异常。(Exception是所有异常的父类,因此也可以捕获所有异常)
except 某个异常类名 :(例如:except ZeroDivisionError : )
如果 except 后面跟着某一个异常的类型,此时它只会捕获到该异常的类型。
注意:如果此时代码中有其他异常,并且其他异常比我们想捕获的异常更早发生,则程序也会立即终止。
注意:如果使用多个“ except 某个异常类名 : ”语句,则可以做到捕获多个不同的异常。 但也只会处理第一个发生的异常。为了防止出现你不知道的异常,也可以在最后加一个捕获所有异常的语句。
except 某个异常类名 as xx :
except … as … 语句可以捕获到异常对象并赋值给 xx 。
try:
print(1+'1')
print(a)
print(2/0)
except ZeroDivisionError as e:
print(e)
except NameError as e:
print(e)
except Exception as e:
print('new exception:',e)
结果为:
new exception: unsupported operand type(s) for +: ‘int’ and ‘str’
假设我们自定义了一个函数或方法,且对其输入的参数有一定要求。当输入的参数不是我们想要的参数类型时,我们可以主动抛出一个异常来阻止程序的运行。
我们可以使用 raise 语句来主动抛出一个异常。
raise 异常类或异常对象
例如:
raise Exception
返回结果为:
Traceback (most recent call last):
File "D:\code\test.py", line 1, in <module>
raise Exception
Exception
我们来结合一下上面学到的 try 语句。
try:
raise Exception
except Exception as e:
print(e)
当我们运行这个语句时,发现一个问题。运行结果竟然只有一个空行!这就很难受了。必须想想办法。
结论:直接用上面的 raise 语句产生的异常对象调用 str() 函数,返回值为空字符串,因此无法直接在捕获时用 print() 函数打印出错误信息。调用 repr() 函数返回值不为空字符串。测试如下:
try:
raise NameError
except Exception as e:
print(type(e.__repr__()),e.__repr__(),sep=':')
print(type(e.__str__()),e.__str__(),sep=':')
:NameError()
:
猜想:实际中使用 raise 语句时,后面的类应该传入一些参数生成有具体信息的实例。
其实,我们只需要给 raise 后面的异常类传入一个字符串参数,就能生成一个带打印信息的异常对象。
try:
raise Exception('发生了一个故意发生的错误')
except Exception as e:
print(e)
结果为:
发生了一个故意发生的错误
我们再来看一下直接使用这个语句的错误信息。
Traceback (most recent call last):
File "D:\code\test.py", line 7, in <module>
raise Exception('发生了一个故意发生的错误')
Exception: 发生了一个故意发生的错误
到此为止,我们的代码已经基本满足了我们的需求,但是还有一个令人不舒服的地方:我们到现在用的都是系统里已有的类,或者是 Exception 这种不够精确的异常类,而针对我们自定义的函数/或方法,我们肯定希望能有一个异常类能直观、清楚地表现,这个异常是因为错误调用我们的自定义函数/方法而产生的。目前的代码,显然不够完美,因此,我们需要一个自定义异常类。
现在,我们已经知道了 Exception 这个类是所有异常类的父类。因此我们在自定义异常类的时候,必须继承 Exception 这个类。
实际上,很多时候,我们只需要自定义一个继承 Exception 类的异常类即可。
class MyError(Exception):
pass
当然,如果我们想自己完善这个类也可以。
例如我们定义一个函数,这个函数只支持整数变量传入,当我们传入非整数变量时,会抛出我们的一个自定义异常类。这样我们又需要定义一个异常类,这个类可以传入一个不为整数的参数,并且报错信息会告诉你,你传入的参数是什么,是什么类型,而我们需要传入一个整数。
代码如下:
class MyError(Exception):
def __init__(self,data):
self.data = data
def __str__(self):
error_message = ('Error:你传入的变量是'+repr(self.data)+',是'
+repr(type(self.data))+'变量,而需要传入int变量')
return error_message
def myfunction(data):
if type(data) != int:
raise MyError(data)
else:
print('已收到:%d'%data)
if __name__ == '__main__':
myfunction('1')
运行结果为:
Traceback (most recent call last):
File "D:\code\test.py", line 21, in <module>
myfunction('1')
File "D:\code\test.py", line 11, in myfunction
raise MyError(data)
MyError: Error:你传入的变量是'1',是<class 'str'>变量,而需要传入int变量
(自己写代码时遇到的一个小易错点:一堆字符串相加生成新字符串时,换行必须让加号在最前面,且必须在整个外面加上小括号,如代码第5、6行。如果换行加号不放在换行后的前面会编译不通过,如果外面不加小括号会产生如下很神奇的报错)
Traceback (most recent call last):
File "D:\code\test.py", line 17, in <module>
myfunction('1')
File "D:\code\test.py", line 11, in myfunction
raise MyError(data)
MyError: <unprintable MyError object>
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "D:\code\test.py", line 19, in <module>
print(e)
File "D:\code\test.py", line 6, in __str__
+repr(type(self.data))+'变量,而需要传入int变量'
TypeError: bad operand type for unary +: 'str'
如果你的报错信息中有 During handling of the above exception, another exception occurred:
,说明你的异常类在报错时发生错误。