目录
请大家运行如下代码
- a = 100/0
- print(a)
就会发现解释器显示如下的错误提示
- Traceback (most recent call last):
- File "xxxxxxxxxx.py", line 1, in
- a = 100/0
- ZeroDivisionError: division by zero
大家要学会看解释器的报错。
这就是解释器向我们报告, 有一个 ZeroDivisionError 错误对象 或者说 异常对象 产生了。
这个 ZeroDivisionError 对象 代表的是一个除以0 的异常。 我们知道0是不能作为除数的。
因为这个问题,解释器没有办法继续执行后面的代码了。所以程序就此结束执行了。
ZeroDivisionError就是一个异常对象的类,继承自标准库里面的 Exception 类。
Python标准库中还有很多其他的异常类 都是继承自标准库里面的 Exception 类,代表各种不同类型的错误。
大家可以在命令行窗口 运行 Python 解释器交互命令行,分别输入如下代码:
xxxx
会产生 NameError,表示xxxx没有定义
- dict1 = {1:1}
- print(dict1[2])
会产生 KeyError,表示该字典没有key为2的元素
import xxxx
会产生 ModuleNotFoundError,表示找不到xxxx这样的模块
解释器执行代码过程中,如果发生异常,就会导致解释器没法继续按照正常流程往下执行代码,所以解释器会结束当前代码的执行。
如果我们在编码的时候,就预料到了某些代码运行时可能出现某些异常,就可以使用 try...except...
这样的方法来捕获和处理异常。
比如,我们要开发程序,实现一个把用户输入的路程长度从英里换算成公里,如下所示
- while True:
- miles = input('请输入英里数:')
- km = int(miles) * 1.609344
- print(f'等于{km}公里')
编写这段代码的时候, 我们就可以预料到,可能用户会输入非数字的字符,这样用int转化就会出错,导致整个程序就退出了。
这时,我们就可以这样写
- while True:
- try:
- miles = input('请输入英里数:')
- km = int(miles) * 1.609344
- print(f'等于{km}公里')
- except ValueError:
- print('你输入了非数字字符')
try 下面缩进的3行代码可以看成是 保护区
中的代码。
如果执行保护区中代码时,出现异常,解释器会结束保护区中后续代码的执行,并检查这个异常的类型是否匹配后面的except 语句中声明的类型。
如果匹配上,解释器知道程序对此种异常是预料到的,并且有对应的处理方案,也就是匹配的except下面缩进的代码。解释器就执行匹配的except下面缩进的代码,不会因此中止程序。
上面的例子中,执行 try 下面缩进的代码时,如果用户输入了 hello
这样的非数字, 就会在这行语句处
km = int(miles) * 1.609344
产生 ValueError 类型的异常, 解释器就会去查看后面的 except 语句是否声明了对 ValueError 异常的处理。
发现有, 就会执行后面缩进的代码。也就是这句代码
print('你输入了非数字字符')
except 后面缩进的代码 就是对这种类型错误 的一种处理。
既然程序已经知道如何处理这种问题, 就不需要结束执行,只需要执行完 处理代码后, 进行原来正常的执行流程。
在这里,就是继续 while True
循环。
如果我们开发程序的时候,估计某个代码段中可能出现好几种类型的异常,可以使用多个except 代码段,分别捕获多种类型的异常,如下
- try:
- choice = input('输入你的选择:')
- if choice == '1':
- 100/0
- elif choice == '2':
- [][2]
- except ZeroDivisionError:
- print ('出现 ZeroDivisionError')
- except IndexError :
- print ('出现 IndexError')
如果 输入'1', 则会产生 ZeroDivisionError
异常, 就会被 except ZeroDivisionError
捕获,执行对应的代码
print ('出现 ZeroDivisionError')
如果 输入'2', 则会产生 IndexError
异常, 就会被 except IndexError
捕获,执行对应的代码
print ('出现 IndexError')
我们使用except 语句匹配异常类型的时候, 可以使用as关键字,后面加一个变量名,如下所示:
- try:
- 100/0
- except ZeroDivisionError as e:
- print (f'异常对象信息:{e}')
这样,运行代码的时候,当try中的语句产生异常对象时,就会 把产生的异常对象赋值给as后的变量。
上面的代码,运行输出
异常对象信息:division by zero
产生的异常对象赋值给了变量 e。
这样我们就可以在后续的代码中得到产生的异常对象的信息。
如果我们在写一段代码的时候,不知道这段代码会抛出什么样的异常,并且我们不希望程序因为异常而中止。
这时我们可以匹配所有类型的异常,这样任何类型的异常发生都不会终止程序了。 如下:
- try:
- 100/0
- except Exception as e:
- print('未知异常:', e)
因为所有的异常都是 Exception
的子类。 所以 Exception能匹配所有类型的异常。
还有一种更简洁的写法,也可以匹配所有类型的异常,如下所示
- try:
- 100/0
- except:
- print('未知异常:')
except 后面 没有写异常的类型,也表示匹配所有的异常。
如果我们想知道异常的详细信息,可以使用traceback模块,如下,
- import traceback
-
- try:
- 100/0
- except :
- print(traceback.format_exc())
在except下面缩进代码中,使用traceback模块里面的format_exc函数,可以显示异常的信息 和 异常产生处的函数调用栈的信息。
上面的代码会打印出导致异常的详细的函数调用栈的信息,如下
- Traceback (most recent call last):
- File "xxxx/xxx.py", line 4, in <module>
- 100/0
- ZeroDivisionError: division by zero
异常类型都是 继承自Exception的类,表示各种类型的错误。
我们也可以自己定义异常,比如我们写一个用户注册的函数, 要求用户输入的电话号码只能是中国的电话号码,并且电话号码中不能有非数字字符。
可以定义下面这两种异常类型:
- # 异常对象,代表电话号码有非法字符
- class InvalidCharError(Exception):
- pass
- # 异常对象,代表电话号码非中国号码
- class NotChinaTelError(Exception):
- pass
定义了上面的异常,当用户输入电话号码时,出现相应错误的时候,我们就可以使用raise 关键字来抛出对应的自定义异常
- def register():
- tel = input('请注册您的电话号码:')
-
- # 如果有非数字字符
- if not tel.isdigit():
- raise InvalidCharError()
-
- # 如果不是以86开头,则不是中国号码
- if not tel.startswith('86'):
- raise NotChinaTelError()
-
- return tel
-
- try:
- ret = register()
- except InvalidCharError:
- print('电话号码中有错误的字符')
- except NotChinaTelError:
- print('非中国手机号码')
大家来看下面的一段代码:
- def level_3():
- print ('进入 level_3')
- a = [0]
- b = a[1]
- print ('离开 level_3')
-
- def level_2():
- print ('进入 level_2')
- level_3()
- print ('离开 level_2')
-
- def level_1():
- print ('进入 level_1')
- level_2()
- print ('离开 level_1')
-
-
- level_1()
-
- print('程序正常退出')
运行该代码会得到类似下面的结果
- 进入 level_1
- 进入 level_2
- 进入 level_3
- Traceback (most recent call last):
- File "E:\err.py", line 18, in <module>
- level_1()
- File "E:\err.py", line 14, in level_1
- level_2()
- File "E:\err.py", line 9, in level_2
- level_3()
- File "E:\err.py", line 4, in level_3
- b = a[1]
- IndexError: list index out of range
函数调用次序是这样的
主体部分调用 函数 level_1
函数level_1调用 函数level_2
函数level_2调用 函数level_3
大家注意:函数 level_3 中有个 列表索引越界的错误。
所以执行到该函数的时候,解释器报错了。它在终端上显示了错误代码的具体位置。 也就是
- File "E:\err.py", line 4, in level_3
- b = a[1]
大家可以发现,上面还有输出的信息,说明了这行引起异常的代码, 是怎样被 一层层 的调用进来的。
这就是函数调用栈的信息。
当异常在函数中产生的时候,解释器会终止当前代码的执行, 查看当前函数是否 声明了该类型异常的 except 处理,如果有,就执行, 随后继续执行代码。
如果当前函数没有 声明了该类型异常的处理, 就会中止当前函数的执行,退出到调用该函数的上层函数中, 查看上层是否有 声明了该类型异常的 except 处理。如果有,就执行该异常匹配处理。 随后继续执行代码。
如果上层函数也没有 该类型异常的匹配处理, 就会到继续到再上层的函数查看是否有 该类型异常的匹配处理。
如此这般,直到到了最外层的代码。 如果依然没有 声明了该类型异常处理,就终止当前代码的执行。
下面的几个示例代码,分别在不同的函数调用层次 捕获异常。 大家可以依次执行一下,看看各自对执行结果有什么影响。
- import traceback
-
- def level_3():
- print ('进入 level_3')
- a = [0]
- b = a[1]
- print ('离开 level_3')
-
- def level_2():
- print ('进入 level_2')
- level_3()
- print ('离开 level_2')
-
- def level_1():
- print ('进入 level_1')
- level_2()
- print ('离开 level_1')
-
-
- try:
- level_1()
- except :
- print(f'未知异常:{traceback.format_exc()}')
-
- print('程序正常退出')
- import traceback
-
- def level_3():
- print ('进入 level_3')
- a = [0]
- b = a[1]
- print ('离开 level_3')
-
- def level_2():
- print ('进入 level_2')
- level_3()
- print ('离开 level_2')
-
- def level_1():
- print ('进入 level_1')
- try:
- level_2()
- except :
- print(f'未知异常:{traceback.format_exc()}')
- print ('离开 level_1')
-
-
- level_1()
-
- print('程序正常退出')
- import traceback
-
- def level_3():
- print ('进入 level_3')
- a = [0]
- b = a[1]
- print ('离开 level_3')
-
- def level_2():
- print ('进入 level_2')
- try:
- level_3()
- except :
- print(f'未知异常:{traceback.format_exc()}')
- print ('离开 level_2')
-
- def level_1():
- print ('进入 level_1')
- level_2()
- print ('离开 level_1')
-
-
- level_1()
-
- print('程序正常退出')
- import traceback
-
- def level_3():
- print ('进入 level_3')
- try:
- a = [0]
- b = a[1]
- except :
- print(f'未知异常:{traceback.format_exc()}')
- print ('离开 level_3')
-
- def level_2():
- print ('进入 level_2')
- level_3()
- print ('离开 level_2')
-
- def level_1():
- print ('进入 level_1')
- level_2()
- print ('离开 level_1')
-
-
- level_1()
-
- print('程序正常退出')