13.1 异常概述
13.1.1 异常的概念
• 在生活中,使用计算机中的某个应用软件时,由于某种错误,可能出现异常,如图所示。
• 在程序中,当Python检测到一个错误时,解释器就会指出当前流程已无法继续执行下去,这时就出现了异常。例如,使用print()函数输出一个未定义的变量值,具体如下所示:
print(name)
1
• 在Python程序中,如果出现异常,而异常对象并未被捕获或处理,程序就会用自动回溯,返回一种错误信息,并终止执行,上述语句返回的错误信息如下:
Traceback (most recent call last):
File “D:/1000phone/test.py”, line 1, in
print(name)
NameError: name ‘name’ is not definen
• 上述信息提示name变量名未定义,NameError为Python的内建异常类。异常是指因为程序出错而在正常控制流以外采取的行为,即异常是一个事件,该事件可能会在程序执行过程中发生并影响程序的正常执行。
13.1.2 异常类
• Python为了区分不同的异常,其中内置了许多异常类,常见的异常类如表所示。
• 在表中,BaseException是异常的顶级类,但用户定义的类不能直接继承这个类而是要继承Exception。Exception类是与应用相关异常的顶层基类,除了系统退出事件类(SystemExit、KeyboardInterrupt和GeneratorExit)之外,几乎所有用户定义的类都应该继承自这个类,而不是BaseException类。
13.2 捕获与处理异常
• 为了防止程序运行中遇到异常而意外终止,开发时应对可能出现的异常进行捕获并处理。Python程序使用try、except、else、finally这4个关键字来实现异常的捕获与处理。
13.2.1 try-except语句
• try-except语句可以捕获异常并进行处理,其语法格式如下:
try:
可能出现异常的语句
except 异常类名:
处理异常的语句
• 当try语句块中某条语句出现异常时,程序就不再执行try语句块中后面的语句,而是直接执行except语句块,如例所示。
1 try:
2 a = float(input(‘请输入被除数:’))
3 b = float(input(‘请输入除数:’))
4 print(a, ‘/’, b, ‘结果为’, a / b)
5 print(‘运算结束’)
6 except ZeroDivisionError:
7 print(‘除数不能为 0’)
8 print(‘程序结束’)
• 需要注意的是,上例程序只能捕捉except后面的异常类,如果发生其他类型异常,程序依然会终止。例如,运行上例程序,输入ab再回车,则程序出现错误,如图所示。
在这里插入图片描述
• 在图中,错误信息提示字符串类型不能转化为浮点型。为了保证程序正常运行,此时就需要捕获并处理多种异常,其语法格式如下:
try:
可能出现异常的语句
except 异常类名 1:
处理异常 1 的语句
except 异常类名 2:
处理异常 2 的语句
…
• 在程序中,虽然开发者可以编写处理多种异常的代码,但异常是防不胜防的,很有可能再出现其他异常,此时就需要捕获并处理所有可能发生的异常,其语法格式如下:
try:
可能出现异常的语句
except 异常类名:
处理异常的语句
except:
与上述异常不匹配时,执行此语句块
• 如果程序发生了异常,但是没有找到匹配的异常类别,则执行不带任何匹配类型的except语句块。
13.2.2 使用as获取异常信息
• 为了区分不同的异常,可以使用as关键字来获取异常信息,其语法格式如下:
try:
可能出现异常的语句
except 异常类名 as 异常对象名:
处理异常的语句
• 如果程序需要获取多种异常信息,则可以使用如下语法格式:
try:
可能出现异常的语句
except (异常类名 1, 异常类名 2, …) as 异常对象名:
处理异常的语句
• 如果程序需要获取所有异常信息,则可以使用如下语法格式:
try:
可能出现异常的语句
except BaseException as 异常对象名:
处理异常的语句
• 所有的异常类都继承自BaseException类,因此上述语句可以获取所有异常信息。
13.2.3 try-except-else语句
• try-except-else语句用于处理未捕获到异常的情形,其语法格式如下:
try:
可能出现异常的语句
except BaseException as 异常对象名:
处理异常的语句
else:
未捕获到异常执行的语句
• 如果try语句内出现了异常,则执行except语句块,否则执行else语句块。
• 接下来演示try-except-else语句的用法,如例所示。
1 try:
2 a = float(input(‘请输入被除数:’))
3 b = float(input(‘请输入除数:’))
4 result = a / b
5 except BaseException as e:
6 print(type(e), e)
7 else:
8 print(a, ‘/’, b, ‘结果为’, result)
9 print(‘程序结束’)
13.2.4 try-finally语句
• 在try-finally语句中,无论try语句块中是否发生异常,finally语句块中的代码都会执行,其语法格式如下:
try:
可能出现异常的语句
finally:
无论是否发生异常都会执行的语句
• 其中,finally语句块用于清理在try块中执行的操作,如释放其占有的资源(如文件对象、数据库连接、图形句柄等)。
• 另外,with-as语句可作为try-finally语句处理异常的替代,其语法格式如下:
with 表达式 [as 变量名]:
with 语句块
• 该语句用于定义一个有终止或清理行为的情况,如释放线程资源、文件、数据库连接等,在这些场合下使用with语句将使代码更加简洁。
• 在讲解文件打开与关闭时,本书使用的就是with-as语句。with后面的表达式的结果将生成一个支持环境管理协议的对象,该对象中定义了__enter__() 和__exit__()方法。在with内部的语句块执行之前调用__enter__()方法运行构造代码,如果在as后面指定了一个变量,则将返回值和这个变量名绑定。当with内部语句块执行结束后,自动调用__exit__()方法,同时执行必要的清理工作,不管执行过程中有无异常发生。
• 以上学习了try-except语句、try-except-else语句和try-finally语句,在实际开发中,经常需要将3种语句结合起来使用,具体如下所示:
try:
可能出现异常的语句
except 异常类名 as 异常对象名:
处理特定异常的语句
except:
处理多个异常的语句
else:
未捕获到异常执行的语句
finally:
无论是否发生异常都会执行的语句
• 程序先执行try语句块,若try语句块中的某一语句执行时发生异常,则程序跳转到except语句,从上到下判断抛出的异常是否与except后面的异常类相匹配,并执行第一个匹配该异常的except后面的语句块。
• 若try语句块中发生了异常,但是没有找到匹配的异常类,则执行不带任何匹配类型的except语句块。
• 若没有发生任何异常,则程序在执行完try语句块后直接进入else语句块。
• 最后,无论程序是否发生异常,都会执行finally语句块。
13.3 触发异常
• 触发异常有两种情况:一种是程序执行中因为错误自动触发异常,另一种是显式地使用raise或assert语句手动触发异常。Python捕获与处理这两种异常的方式是相同的。本节主要介绍手动触发异常。
13.3.1 raise语句
• raise语句可以手动触发异常,其使用方法有如下3种。
• 1. 通过类名触发异常
• 该方法只需指明异常类便可创建异常类的实例对象并触发异常,其语法格式如下:
raise 异常类名
1
• 例如,手动触发语法错误异常,则可以使用以下语句:
raise SyntaxError
1
• 程序运行时,输出以下信息:
Traceback (most recent call last):
File “D:/1000phone/test.py”, line 1, in
raise SyntaxError
SyntaxError: None
• 2. 通过异常类的实例对象触发异常
• 该方法只需指明异常类的实例对象便可触发异常,其语法格式如下:
raise 异常类的实例对象
• 例如,手动触发除零导致的异常,则可以使用以下语句:
raise ZeroDivisionError()
1
• 程序运行时,输出以下信息:
Traceback (most recent call last):
File “D:/1000phone/test.py”, line 1, in
raise ZeroDivisionError()
ZeroDivisionError
• 此外,该方法还可以指定异常信息,具体如下所示:
raise ZeroDivisionError(‘除数为零!’)
1
• 程序运行时,输出以下信息:
Traceback (most recent call last):
File “D:/1000phone/test.py”, line 1, in
raise ZeroDivisionError(‘除数为零!’)
ZeroDivisionError: 除数为零!
• 3. 重新触发异常
• raise语句还可以重新触发异常,具体如下所示:
try:
raise ZeroDivisionError
except:
print(‘捕捉到异常!’)
raise # 重新触发刚才发生的异常
• 程序运行时,输出以下信息:
Traceback (most recent call last):
捕捉到异常!
File “D:/1000phone/test.py”, line 2, in
raise ZeroDivisionError
• 可以看出,程序执行了except语句块中的代码,其中的raise语句会重新触发ZeroDivisionError异常,但此时异常对象并未被捕获或处理,因此程序终止运行。
13.3.2 assert语句
• assert语句(又称断言)是有条件的触发异常,其语法格式如下:
assert 表达式 [, 参数]
1
• 其中,当表达式为真时,不触发异常;当表达式为假时,触发AssertionError异常。若给定了参数部分,则在AssertionError后将参数部分作为异常信息的一部分给出。
• assert语句的主要功能是帮助程序员调试程序,以保证程序运行的正确性,因此它一般在开发调试阶段使用。
• 接下来演示assert语句的用法,如例所示。
1 try:
2 a = float(input(‘请输入被除数:’))
3 b = float(input(‘请输入除数:’))
4 assert a >= b, ‘被除数大于除数’
5 result = a / b
6 except BaseException as e:
7 print(e.class.name, ‘:’, e)
8 print(‘程序结束’)
13.4 自定义异常
• Python中内置的异常类毕竟有限,用户有时须根据需求需设置其他异常,如学生成绩不能为负数、限定密码长度等。自定义异常类一般继承于Exception或其子类,其命名一般以Error或Exception为后缀,如例所示。
1 class NumberError(Exception): # 自定义异常类,继承于 Exception
2 def init(self, data = ‘’):
3 Exception.init(self, data)
4 self.data = data
5 def str(self): # 重载__str__方法
6 return self.class.name + ‘:’ + self.data + ‘非法数值(<= 0)’
7 try:
8 num = input(‘请输入正数:’)
9 if float(num) <= 0:
10 raise NumberError(str(num)) # 触发异常
11 print(‘输入的正数为:’, num)
12 except BaseException as e:
13 print(e)
13.5 回溯最后的异常
• 当触发异常时,Python可以回溯异常并提示许多信息,这可能会给程序员定位异常位置带来不便,因此,Python中可以使用sys模块中exc_info()函数来回溯最后一次异常信息,该函数返回一个元组(type,value/message, traceback),每个元素的具体含义如下所示:
• type:异常的类型。
• value/message:异常的信息或者参数。
• traceback:包含调用栈信息的对象。
• 接下来演示该函数的用法,如例所示。
1 import sys # 导入 sys 模块
2 try:
3 4 / 0
4 except:
5 tuple = sys.exc_info()
6 print(tuple)