AI人工智能技术之异常
本章学习目标
• 理解异常的概念
• 掌握异常的处理
• 掌握触发异常
• 掌握自定义异常
异常是指程序运行时引发的错误,引发错误的原因有多种,例如语法错误、除数为零、打开不存在文件等。若这些错误没有进行处理,则会导致程序终止运行,而合理地使用异常处理错误可以使程序具有更强的容错性。
13.%2 异常概述
13.1.1 异常的概念
在生活中,使用计算机中的某个应用软件时,由于某种错误,可能会引发异常,如图13.1所示。
图13.1 程序异常
在程序中,当Python检测到一个错误时,解释器就会指出当前流程已无法继续执行下去,这时就出现了异常,例如,使用print()函数输出一个未定义的变量值,具体如下所示:
print(name)
在Python程序中,如果出现异常,而异常对象并未被捕捉或处理,程序就会用自动的回溯,返回一种错误信息,并终止执行,上述语句返回的错误信息如下:
Traceback (most recent call last):
File "D:/1000phone/test.py", line 1, in <module>
print(name)
NameError: name 'name' is not defined
上述信息提示name变量名未定义,NameError为Python的内建异常类。异常是指因为程序出错而在正常控制流以外采取的行为,即异常是一个事件,该事件会在程序执行过程中发生,影响了程序的正常执行。
13.1.2 异常类
Python为了区分不同的异常,其中内置了许多异常类,常见的异常类如表13.1所示。
表13.1 常见的异常类
异常类名称 |
基类 |
说明 |
BaseException |
object |
所有异常类的直接或间接基类 |
Exception |
BaseException |
所有非退出异常的基类 |
SystemExit |
BaseException |
程序请求退出时抛出的异常 |
KeyboardInterrupt |
BaseException |
用户中断执行(通常是输入Ctrl+C)时抛出 |
GeneratorExit |
BaseException |
生成器发生异常,通知退出 |
ArithmeticError |
Exception |
所有数值计算错误的基类 |
FloatingPointError |
ArithmeticError |
浮点运算错误 |
OverflowError |
ArithmeticError |
数值运算超出最大限制 |
ZeroDivisionError |
ArithmeticError |
除零导致的异常 |
AssertionError |
Exception |
断言语句失败 |
AttributeError |
Exception |
对象没有这个属性 |
EOFError |
Exception |
读取超过文件结尾 |
OSError |
Exception |
I/O相关错误的基类 |
ImportError |
Exception |
导入模块/对象失败 |
LookupError |
Exception |
查找错误的基类 |
IndexError |
LookupError |
序列中没有此索引 |
KeyError |
LookupError |
映射中没有这个键 |
MemoryError |
Exception |
内存溢出错误 |
NameError |
Exception |
未声明、未初始化对象 |
UnboundLocalError |
NameError |
访问未初始化的本地变量 |
ReferenceError |
Exception |
弱引用试图访问已经垃圾回收了的对象 |
RuntimeError |
Exception |
一般的运行时错误 |
NotImplementedError |
RuntimeError |
尚未实现的方法 |
SyntaxError |
Exception |
语法错误 |
IndentationError |
SyntaxError |
缩进错误 |
TabError |
IndentationError |
Tab 和空格混用 |
SystemError |
Exception |
一般的解释器系统错误 |
TypeError |
Exception |
对类型无效的操作 |
ValueError |
Exception |
传入无效的参数 |
Warning |
Exception |
警告的基类 |
RuntimeWarning |
Warning |
可疑的运行时行为警告基类 |
SyntaxWarning |
Warning |
可疑的语法警告基类 |
在表13.1中,BaseException是异常的顶级类,但用户定义的类不能直接继承这个类,而是要继承Exception。Exception类是与应用相关异常的顶层基类,除了系统退出事件类之外(SystemExit、KeyboardInterrupt和GeneratorExit),几乎所有用户定义的类都应该继承自这个类,而不是BaseException类。
13.%2 捕获与处理异常
为了防止程序运行中遇到异常而意外终止,编程时应对可能出现异常进行捕获并处理。在Python程序中,使用try、except、else、finally四个关键字来实现异常的捕捉与处理。
13.2.1 try-except语句
try-except语句可以捕获异常并进行处理,其语法格式如下:
try:
# 可能出现异常的语句
except 异常类名:
# 处理异常的语句
当try语句块中某条语句出现异常时,程序就不再执行try语句块后面的语句,而是直接执行except语句块中的语句,如例13-1所示。
例13-1
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('程序结束')
程序运行时,输入4与2,则运行结果如图13.2所示。
图13.2 运行结果
再次运行程序,输入4与0,则运行结果如图13.3所示。
图13.3 运行结果
从两次运行结果可看出,程序没有触发异常与触发异常执行的流程并不一致。程序中一旦发生异常,就不会执行try语句块中剩余的语句,而是直接执行except语句块。另外,本程序捕捉并处理了异常,因此,当输入的除数为0时,程序可以正常结束,而不是终止运行。
读者需注意,上例程序只能捕捉except后面的异常类,如果发生其他类型异常,程序依然会终止,例如,运行上例程序,输入ab再回车,则程序出现错误,如图13.4所示。
图13.4 错误信息
在图13.4中,错误信息提示字符串类型不能转化为浮点型,为了保证程序正常运行,此时就需要捕获并处理多个异常,其语法格式如下:
try:
# 可能出现异常的语句
except 异常类名1:
# 处理异常1的语句
except 异常类名2:
# 处理异常2的语句
...
接下来演示捕获并处理多种异常,如例13-2所示。
例13-2
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 except ValueError:
9 print('传入参数无效')
10 print('程序结束')
程序运行时,输入ab并回车,则运行结果如图13.5所示。
图13.5 运行结果
在例13-2中,程序中增加了处理ValueError异常。在程序中,虽然开发者可以编写处理多种异常的代码,但异常是防不胜防的,很有可能再出现其他异常,此时就需要捕捉并处理所有可能发生的异常,其语法格式如下:
try:
# 可能出现异常的语句
except 异常类名:
# 处理异常的语句
except:
# 与上述异常不匹配时,执行此语句块
如果程序发生了异常,但是没有找到匹配的异常类别,则执行不带任何匹配类型的except语句后面的语句块。
接下来演示捕获并处理所有异常,如例13-3所示。
例13-3
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 except:
9 print('其他错误')
10 print('程序结束')
程序运行时,输入ab并回车,则运行结果如图13.6所示。
图13.6 运行结果
在例13-3中,第8行通过except语句可以处理除ZeroDivisionError异常外的其他所有异常。
13.2.2 使用as获取异常信息
为了区分不同的异常,可以使用as关键字来获取异常信息,其语法格式如下:
try:
# 可能出现异常的语句
except 异常类名 as 异常对象名:
# 处理异常的语句
通过异常对象名便可以访问异常信息,如例13-4所示。
例13-4
1 try:
2 a = float(input('请输入被除数:'))
3 b = float(input('请输入除数:'))
4 print(a, '/', b, '结果为', a / b)
5 print('运算结束')
6 except ZeroDivisionError as e:
7 print(type(e), e)
8 print('程序结束')
运行结果如图13.7所示。
图13.7 运行结果
在例13-4中,第6行通过as关键字可以获取ZeroDivisionError异常类的实例对象e,第7行通过print()函数打印异常信息。
若捕捉并获取多种异常信息,则可以使用如下如法格式:
try:
# 可能出现异常的语句
except (异常类名1, 异常类名2, ...) as 异常对象名:
# 处理异常的语句
接下来演示捕捉并获取多种异常信息,如例13-5所示。
例13-5
1 try:
2 a = float(input('请输入被除数:'))
3 b = float(input('请输入除数:'))
4 print(a, '/', b, '结果为', a / b)
5 print('运算结束')
6 except (ZeroDivisionError, ValueError) as e:
7 print(type(e), e)
8 print('程序结束')
程序运行时,输入4与0,则运行结果如图13.8所示。
图13.8 运行结果
再次运行程序,输入ab,则运行结果如图13.9所示。
图13.9 运行结果
从上述运行结果可看出,当程序出现ZeroDivisionError或ValueError异常时,第6行语句会自动捕捉相应的异常并生成该异常类的实例对象。
若捕捉并获取所有异常信息,则可以使用如下如法格式:
try:
# 可能出现异常的语句
except BaseException as 异常对象名:
# 处理异常的语句
所有的异常类都继承自BaseException类,因此上述语句可以捕捉并获取所有异常信息,如例13-6所示。
例13-6
1 try:
2 a = float(input('请输入被除数:'))
3 b = float(input('请输入除数:'))
4 print(a, '/', b, '结果为', a / b)
5 print('运算结束')
6 except BaseException as e:
7 print(type(e), e)
8 print('程序结束')
运行结果如图13.10所示。
图13.10 运行结果
在例13-6中,第6行语句可以捕捉并获取所有异常,但不建议在程序中直接捕捉所有异常,因为它会隐藏所有程序员未想到并且未做好准备处理的错误。
13.2.3 try-except-else语句
try-except-else语句用于处理未捕捉到异常的情形,其语法格式如下:
try:
# 可能出现异常的语句
except BaseException as 异常对象名:
# 处理异常的语句
else:
# 未捕捉到异常执行的语句
如果try语句内出现了异常,则执行except语句块,否则执行else语句块。
接下来演示try-except-else语句的用法,如例13-7所示。
例13-7
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('程序结束')
程序运行时,输入4与0,则运行结果如图13.11所示。
图13.11 运行结果
再次运行程序,输入4与2,则运行结果如图13.12所示。
图13.12 运行结果
在例13-7中,当未捕捉到异常时,程序执行else语句。
13.2.4 try-finally语句
在try-finally语句中,无论try语句块中是否发生异常,finally语句块中的代码都会执行,其语法格式如下:
try:
# 可能出现异常的语句
finally:
# 无论是否发生异常都会执行的语句
其中,finally语句块用于清理在try块中执行的操作,如释放其占有的资源(如文件对象、数据库连接、图形句柄等)。
接下来演示try-finally语句的用法,如例13-8所示。
例13-8
1 try:
2 f = open('test.txt', 'a+')
3 i = 1
4 while True:
5 str = input('请输入第%d行字符串(按Q结束):'%i)
6 if str.upper() == 'Q':
7 break
8 f.write(str + '\n')
9 i += 1
10 except KeyboardInterrupt:
11 print('程序中断!(Ctrl+C)')
12 finally:
13 f.close()
14 print('文件关闭')
15 print('程序结束')
打开控制台(window+R打开运行窗口,在输入框中输入cmd并点击确定),在命令行模式下进入D:\1000phone目录中,输入“python 13-8.py”,开始执行程序,如图13.13所示。
图13.13 在命令行模式中执行程序
当提示输入第3行字符串时,在键盘中按下Ctrl+C键,此时引发KeyboardInterrupt异常,程序立即执行except语句,之后再执行finally语句。
另外,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语句,在实际开发中,经常需要将三种语句结合起来使用,具体如下所示:
try:
# 可能出现异常的语句
except 异常类名 as 异常对象名:
# 处理特定异常的语句
except:
# 处理多个异常的语句
else:
# 未捕捉到异常执行的语句
finally:
# 无论是否发生异常都会执行的语句
程序先执行try语句块,若try语句块中的某一语句执行时发生异常,则程序跳转到except语句,从上到下判断抛出的异常是否与except后面的异常类相匹配,并执行第一个匹配该异常的except后面的语句块。
若try语句块中发生了异常,但是没有找到匹配的异常类别,则执行不带任何匹配类型的except语句块。
若没有发生任何异常,则程序在执行完try语句块后直接进入else语句块。
最后,无论程序是否发生异常,都会执行finally语句块。
13.%2 触发异常
触发异常有两种情况:一种是程序执行中因为错误自动触发异常,另一种是显式地使用raise或assert语句手动触发异常。Python捕捉与处理这两种异常的方式是相同的。本节主要介绍手动触发异常。
13.3.1 raise语句
raise语句可以手动触发异常,其使用方法有三种,具体如下所示:
1. 通过类名触发异常
该方法只需指明异常类便可创建异常类的实例对象并触发异常,其语法格式如下:
raise 异常类名
例如,手动触发语法错误异常,则可以使用以下语句:
raise SyntaxError
程序运行时,输出以下信息:
Traceback (most recent call last):
File "D:/1000phone/test.py", line 1, in <module>
raise SyntaxError
SyntaxError: None
2. 通过异常类的实例对象触发异常
该方法只需指明异常类的实例对象便可触发异常,其语法格式如下:
raise 异常类的实例对象
例如,手动触发除零导致的异常,则可以使用以下语句:
raise ZeroDivisionError()
程序运行时,输出以下信息:
Traceback (most recent call last):
File "D:/1000phone/test.py", line 1, in <module>
raise ZeroDivisionError()
ZeroDivisionError
此外,该方法还可以指定异常信息,具体如下所示:
raise ZeroDivisionError('除数为零!')
程序运行时,输出以下信息:
Traceback (most recent call last):
File "D:/1000phone/test.py", line 1, in <module>
raise ZeroDivisionError('除数为零!')
ZeroDivisionError: 除数为零!
3. 重新触发异常
raise语句还可以重新触发异常,具体如下所示:
try:
raise ZeroDivisionError
except:
print('捕捉到异常!')
raise # 重新触发刚才发生的异常
程序运行时,输出以下信息:
Traceback (most recent call last):
捕捉到异常!
File "D:/1000phone/test.py", line 2, in <module>
raise ZeroDivisionError
从上可以看出,程序执行了except语句块中的代码,其中的raise语句会重新触发ZeroDivisionError异常,但此时异常对象并未被捕捉或处理,因此程序终止运行。
13.3.2 assert语句
assert语句(又称断言)是有条件的触发异常,其语法格式如下:
assert 表达式 [, 参数]
其中,当表达式为真时,则不发生任何事情,当表达式为假时,则触发AssertionError异常。若给定了参数部分,则在AssertionError后将参数部分作为异常信息的一部分给出。
assert语句的主要功能是帮助程序员调试程序,以保证程序运行的正确性,因此它一般在开发调试阶段使用。
接下来演示assert语句的用法,如例13-9所示。
例13-9
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('程序结束')
程序运行时,输入4与2,则运行结果如图13.14所示。
图13.14 运行结果
再次运行程序,输入2与4,则运行结果如图13.15所示。
图13.15 运行结果
在例13-9中,只有当输入的被除数小于除数时,程序才会触发AssertionError异常。
13.%2 自定义异常
Python中内置的异常类毕竟有限,用户有时根据需求需设置其他异常,如学生成绩不能为负数、限定密码长度等。自定义异常类一般继承于Exception或其子类,其命名一般以Error或Exception为后缀,如例13-10所示。
例13-10
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.16所示。
图13.16 运行结果
在例13-10中,第1行自定义异常类NumberError,继承于Exception,第10行通过raise语句手动触发NumberError异常。
13.%2 回溯最后的异常
当触发异常时,Python可以回溯异常并给提示许多信息,这可能会给程序员定位异常位置带来不便,因此,Python中可以使用sys模块中exc_info()函数来回溯最后一次异常信息,该函数返回一个元组(type, value/message, traceback),每个元素的具体含义如下所示:
• type:异常的类型
• value/message:异常的信息或者参数
• traceback:包含调用栈信息的对象
接下来演示该函数的用法,如例13-11所示。
例13-11
1 import sys # 导入sys模块
2 try:
3 4 / 0
4 except:
5 tuple = sys.exc_info()
6 print(tuple)
运行结果如图13.17所示。
图13.17 运行结果
在例13-11中,第6行输出sys.exc_info()函数的返回值。该函数虽然可以获取最后触发异常的信息,但是难以直接确定触发异常的代码位置。
13.%2 小案例
计算test.txt文件中每行数字的总和与平均值,该文件可能不存在或为空,也可能某行不包含数字。程序中需捕获并处理可能发生的异常(不能使用with-as语句),具体实现如例13-12所示。
例13-12
1 sum, num, flag = 0, 0, True
2 try:
3 file = open('test.txt', 'r')
4 except FileNotFoundError as e: # 处理文件不存在的异常
5 print(e.__class__.__name__, ':', e)
6 flag = False
7 if flag:
8 try:
9 for line in file:
10 num += 1
11 sum += float(line)
12 ave = sum / num
13 except ValueError as e: # 处理某行不是数字的异常
14 print(num, '行 ', e.__class__.__name__, ':', e)
15 if num > 1:
16 print(num, '行之前:')
17 print('总和:', sum, ' 平均值:', sum / (num - 1))
18 else:
19 print('无法计算')
20 except ZeroDivisionError: # 处理空文件的异常
21 print('空文件')
22 else:
23 print('总和:', sum, ' 平均值:', ave)
24 finally:
25 file.close()
26 print('程序结束')
若test.txt文件不存在,则程序运行结果如图13.18所示。
图13.18 运行结果
若test.txt文件内容如下所示:
12
76
扣丁学堂
23
则程序运行结果如图13.19所示。
图13.19 运行结果
在例13-12中,程序通过try-except语句和try-except-else-finally语句分别对文件操作或除法操作中可能出现的异常进行捕捉和处理。
13.%2 本章小结
本章主要介绍了异常,包括异常的概念、触发异常、捕获与处理异常、自定义异常、回溯最后的异常。学习完本章知识,读者需理解异常处理的作用,即它使程序能够正常执行,不至于程序因异常导致退出或崩溃。
13.%2 习题
1.填空题
(1) 用户自定义的异常类需继承 。
(2) Exception类的基类是 。
(3) 语句是有条件的触发异常。
(4) sys模块中 函数来回溯最后一次异常信息。
(5) 通过 关键字可以获取异常信息。
2.选择题
(1) 下列选项中,( )语句可以手动触发异常。
A.try B.except
C.raise D.finally
(2) 当try语句块中未触发异常时,( )语句块不会执行。
A.finally B.except
C.else D.try
(3) 下列选项中,语句顺序正确的是( )。
A.try-else-except B.try-except-finally-else
C.try-finally-except D.try-except-else-finally
(4) 无论try语句块中是否发生异常,( )语句块都会被执行。
A.except B.except-as
C.finally D.else
(5) SyntaxError表示出现( )异常。
A.语法错误 B.缩进错误
C.除零错误 D.断言
3.思考题
(1) 简述异常处理语句的执行流程。
(2) 简述raise语句的使用方法。
4.编程题
输入与输出员工的姓名、年龄、月收入(输出年收入),假设姓名字符串长度在2-18之间,年龄在18-60之间,月收入大于2500,如果不满足上述条件,则手动触发异常并处理。
- 点赞
- 收藏
- 关注作者
评论(0)