异常处理机制,在程序运行出现错误时,让Python解释器执行事先准备好的除错程序,进而尝试恢复程序的执行
借助异常处理机制,可以做到在程序崩溃前将内存中的数据写入文件、关闭打开的文件、释放分配的内存等
Python异常处理机制,可以让程序中的异常处理代码和正常业务代码分离,,提高程序的健壮性
语法错误
SyntaxError
语法错误属于真正意义上的错误,只有将程序中的所有语法错误全部纠正,程序才能执行
运行时错误
程序在语法上是正确的,但在运行时发生了错误,把这种运行时产生错误的情况叫做异常(Exceptions)
常见异常类型
异常类型 | 含义 |
---|---|
AssertionError | 当 assert 关键字后的条件为假时,程序运行会停止并出现异常 |
AttributeError | 当访问的对象属性不存在 |
IndexError | 索引超出序列范围 |
KeyError | 字典中查找一个不存在的关键字 |
NameError | 访问一个未声明的变量 |
TypeError | 不同类型数据之间的无效操作 |
ZeroDivisionError | 除法运算中除数为 0 |
try:
#业务代码
...
except (Error1, Error2, ...) as e:
alert 输入不合法
goto retry
1.在执行try块里的业务代码时出现异常,系统自动生成一个异常对象,会被提交给Python解释器,这个过程被称为引发异常
2.当解释器收到异常对象时,寻找处理该异常对象的except块,找到合适的 except块,则把该异常对象交给该except块处理,这个过程被称为捕获异常
3.如果解释器找不到捕获异常的except块,则运行时环境终止,解释器退出
4.不管程序代码块是否处于try块中,包括except块中的代码;只要执行该代码块时出现了异常,系统总会自动生成一个 Error 对象
5.如果程序没有为这段代码定义任何的 except 块,解释器无法找到处理该异常的except块,程序在此退出
每个except块都是专门用于处理该异常类及其子类的异常实例
当解释器接收到异常对象后,会依次判断该异常对象是否是 except 块后的异常类或其子类的实例,如果是,将调用该except块;否则,再次将该异常对象和下一个except块里的异常类进行比较
Python的所有异常类都从 BaseException 派生而来,有丰富的异常类,这些异常类之间有严格的继承关系
Python的所有异常类的基类是 BaseException,如果要实现自定义异常,应该继承 Exception 类
BaseException 的主要子类就是Exception,不管是系统的异常类,还是用户自定义的异常类,都应该从Exception派生
import sys
try:
a = int(sys.argv[1])
b = int(sys.argv[2])
c = a / b
print("输入的两个数相除的结果是:", c )
except IndexError:
print("索引错误:输入的参数个数不够")
except ValueError:
print("数值错误:只能接收整数参数")
except ArithmeticError:
print("算术错误")
except Exception:
print("未知异常")
导入sys模块,通过 sys 模块的argv列表来获取运行程序时提供的参数
sys.argv[0] 通常代表正在运行的程序名,sys.argv[1]代表运行程序所提供的第一个参数,sys.argv[2]代表运行程序所提供的第二个参数……
在运行该程序时输入的参数不够,会发生索引错误,调用 IndexError 对应的 except 块处理该异常
在运行该程序时输入的参数不是数字,而是字母,将发生数值错误,调用 ValueError 对应的 except 块处理该异常
在运行该程序时输入的第二个参数是 0,将发生除 0 异常,调用 ArithmeticError 对应的 except 块处理该异常
在程序运行时出现其他异常,该异常对象总是 Exception 类或其子类的实例,调用 Exception 对应的 except 块处理该异常
进行异常捕获时应该把Exception类对应的except块放在最后,所有父类异常的except块都应该排在子类异常的except块的后面
先捕获小异常,再捕获大异常
在使用一个 except 块捕获多种类型的异常时,将多个异常类用圆括号括起来,中间用逗号隔开,就是构建多个异常类的元组
import sys
try:
a = int(sys.argv[1])
b = int(sys.argv[2])
c = a / b
print("输入的两个数相除的结果是:", c )
except (IndexError, ValueError, ArithmeticError):
print("程序发生了数组越界、数字格式异常、算术异常之一")
except: #*
print("未知异常")
*只有except关键字,省略异常类的except语句也是合法的,表示可捕获所有类型的异常,一般会作为异常捕获的最后一个except块
在 except 块中访问异常对象的相关信息,通过为异常对象声明变量来实现
所有的异常对象都包含了如下几个常用属性和方法:
args:该属性返回异常的错误编号和描述字符串
errno:该属性返回异常的错误编号
strerror:该属性返回异常的描述宇符串
with_traceback():通过该方法可处理异常的传播轨迹信息
def test():
try:
fis = open("you.txt");
except Exception as e:
print(e.args) # 访问异常的错误编号和详细信息
print(e.errno) # 访问异常的错误编号
print(e.strerror) # 访问异常的详细信息
test()
(2, 'No such file or directory')
2
No such file or directory
访问异常对象,在单个异常类或异常类元组(多异常捕获)之后使用 as 加上异常变量
在Python的异常处理流程中还可添加一个 else 块,当 try 块没有出现异常时,程序会执行 else 块
当 try 块没有异常,而 else 块有异常时,体现出 else 块的作用
else 必须和 try except 搭配使用
def else_test():
s = input('输入除数:')
result = 20 / int(s)
print('20除以%s的结果是: %g' % (s , result))
def wrong_main():
try:
print('try块的代码,没有异常')
else_test() # 将else_test放在try块代码的后面
except:
print('程序出现异常')
def right_main():
try:
print('try块的代码,没有异常')
except:
print('程序出现异常')
else:
else_test() # 将else_test放在else块中
wrong_main()
right_main()
try块的代码,没有异常
输入除数:0
程序出现异常
try块的代码,没有异常
输入除数:0
ZeroDivisionError: division by zero
定义了一个else_test() 函数,在运行时需要接收用户输入的参数,输入数据的不同可能导致异常
定义了right_main() 和 wrong_main() 两个函数,right_main() 将 else_test() 函数放在 else 块内;wrong_main() 将 else_test() 函数放在 try 块的代码的后面
当 try 块和 else 块都没有异常时,将 else_test() 函数放在 try 块的代码的后面和放在 else 块中没有任何区别
如果输入的数据让 else_test() 函数出现异常(try 块没有异常)
将 else_test() 函数放在 try 块的代码的后面,此时 else_test() 函数运行产生的异常将会被 try 块对应的 except 捕获,这是异常处理机制的执行流程
将 else_test() 函数放在 else 块中,当 else_test() 函数出现异常时,程序没有 except 块来处理该异常,该异常会传播给解释器,导致程序中止
放在 else 块中的代码所引发的异常不会被 except 块捕获,如果希望某段代码的异常能被后面的 except 块捕获,那么就应该将这段代码放在 try 块的代码之后
finally只要求和try搭配使用,没有规定该结构中是否包含except和 else(else 必须和 try except 搭配使用)
finally语句在异常处理机制中的功能:无论 try 块是否发生异常,都要进入 finally语句,执行其中的代码块
当try块中的程序打开了一些物理资源(文件、数据库连接等)时,由于这些资源必须手动回收,回收工作通常就放在finally块中
Python垃圾回收机制只能回收变量、类对象占用的内存,无法自动完成关闭文件、数据库连接等这些的工作
try:
a = int(input("输入 a 的值:"))
print(20/a)
except:
print("发生异常")
else:
print("执行 else 块中的代码")
finally :
print("执行 finally 块中的代码")
输入 a 的值:a
发生异常
执行 finally 块中的代码
当try块发生异常,且没有合适的except处理异常时,finally 块中的代码也会得到执行
try:
print(20/0)
finally :
print("执行 finally 块中的代码")
执行 finally 块中的代码
ZeroDivisionError: division by zero
当try块中代码发生异常,在程序崩溃前Python解释器也会执行finally块中的代码
exc_info()会将当前的异常信息以元组的形式返回,该元组中包含 3 个元素,为 type、value 和 traceback
type:异常类型的名称,是BaseException的子类
value:捕获到的异常实例
traceback:一个traceback对象
import sys #引入sys模块
try:
x = int(input("输入一个被除数:"))
print("30除以",x,"等于",30/x)
except:
print(sys.exc_info())
print("其他异常")
输入一个被除数:0
(<class 'ZeroDivisionError'>, ZeroDivisionError('division by zero',), <traceback object at 0x02B5FFA8>)
其他异常
第 1 个元素是一个ZeroDivisionError 类
第 2 个元素是异常类型 ZeroDivisionError 类的一个实例
第 3 个元素为一个 traceback 对象
通过前2个元素可以看出抛出的异常类型以及描述信息,第 3 个元素是一个 traceback 对象,无法直接看出有关异常的信息,还需要对其做进一步处理
traceback模块可以用来查看异常的传播轨迹,追踪异常触发的源头
在实际应用程序的开发中,一个大的功能需要由多个函数或方法来共同实现,在最终编程模型中,很多对象将通过一系列函数或方法调用来实现通信,执行任务
当应用程序运行时,经常会发生一系列函数或方法调用,异常的传播则相反,只要异常没有被完全捕获(异常没有被捕获,或者异常被处理后重新引发了新异常)
异常就从发生异常的函数或方法逐渐向外传播,先传给该函数或方法的调用者,该函数或方法的调用者再传给其调用者,最后传到解释器,此时解释器会中止该程序,并打印异常的传播轨迹信息
用traceback模块查看异常传播轨迹,首先将 traceback 模块引入,提供了两个常用方法:
traceback.print_exc():将异常传播轨迹信息输出到控制台或指定文件中
format_exc():将异常传播轨迹信息转换成字符串
常用的 print_exc() 是 print_exc([limit[, file]]) 省略了 limit、file 两个参数的形式
print_exc([limit[, file]]) 的完整形式是 print_exception(etype, value, tb[,limit[, file]])
etype:指定异常类型
value:指定异常值
tb:指定异常的traceback 信息
print_exc([limit[, file]]) 相当于如下形式:
print_exception(sys.exc_etype, sys.exc_value, sys.exc_tb[, limit[, file]])
print_exc([limit[, file]]) 会自动处理当前 except 块所捕获的异常
limit:用于限制显示异常传播的层数,比如函数 A 调用函数 B,函数 B 发生了异常,如果指定 limit=1,只显示函数 A 里面发生的异常,如果不设置则默认全部显示
file:指定将异常传播轨迹信息输出到指定文件中,如果不指定该参数,则默认输出到控制台
import traceback # 导入trackback模块
class SelfException(Exception): pass
def main():
firstMethod()
def firstMethod():
secondMethod()
def secondMethod():
thirdMethod()
def thirdMethod():
raise SelfException("自定义异常信息")
try:
main()
except:
traceback.print_exc() # 捕捉异常,并将异常传播信息输出控制台
traceback.print_exc(file=open('log.txt', 'a')) # 捕捉异常,并将异常传播信息输出指定文件中
先导入traceback模块,接下来使用except捕获程序的异常,并使用traceback 的 print_exc() 方法输出异常传播信息,将它输出到控制台和指定文件中
运行上面程序,可以看到在控制台输出异常传播信息,在程序目录下生成了一个 log.txt 文件,该文件中同样记录了异常传播信息
使用raise语句在程序中手动设置异常
raise [exceptionName [(reason)]]
用[]括起来的为可选参数,其作用是指定抛出的异常名称,以及异常信息的相关描述。如果可选参数全部省略,则 raise 会把当前错误原样抛出;如果仅省略 (reason),则在抛出异常时,将不附带任何的异常描述信息
raise 语句三种常用用法:
raise:单独一个raise引发当前上下文中捕获的异常(比如except 块中)或默认引发 RuntimeError 异常
raise 异常类名称:raise 后带一个异常类名称,表示引发执行类型的异常
raise 异常类名称(描述信息):在引发指定类型的异常的同时,附带异常的描述信息
raise 语句引发的异常通常用 try except(else finally)异常处理结构来捕获并进行处理
try:
a = input("输入一个数:")
if(not a.isdigit()): #判断输入的是否为数字
raise ValueError("a 必须是数字")
except ValueError as e:
print("引发异常:",repr(e))
输入一个数:a
引发异常: ValueError('a 必须是数字',)
当输入的不是数字时,进入 if 判断语句,并执行 raise 引发 ValueError 异常,因为其位于 try 块中,raise抛出的异常会被try捕获,并由except块进行处理
虽然程序中使用了 raise 语句引发异常,但程序的执行是正常的,手动抛出的异常并不会导致程序崩溃
使用 raise 语句时可以不带参数
try:
a = input("输入一个数:")
if(not a.isdigit()): #判断输入的是否为数字
raise ValueError("a 必须是数字")
except ValueError as e:
print("引发异常:",repr(e))
raise
位于except块中的 raise,在之前已经手动引发了 ValueError 异常,这里再使用 raise 语句时,会再次引发一次
try:
a = input("输入一个数:")
if(not a.isdigit()): #判断输入的是否为数字
raise
except RuntimeError as e:
print("引发异常:",repr(e))
输入一个数:a
引发异常: RuntimeError('No active exception to reraise',)
在没有引发过异常的程序使用无参的 raise 语句,默认引发的是 RuntimeError 异常
不要使用异常处理来代替正常的业务逻辑判断
异常处理机制的初衷是将不可预期异常的处理代码和正常的业务逻辑处理代码分离
不要过度使用异常
过度使用异常
1.把异常和普通错误混淆在一起,不编写任何错误处理代码,以简单地引发异常来代替所有的错误处理
2.使用异常处理来代替流程控制
异常只应该用于处理非正常的情况,不要使用异常处理来代替正常的流程控制
对于可预知,而且处理方式清楚的错误,程序应该提供相应的错误处理代码,而不是将其笼统地称为异常
不要使用过于庞大的 try 块
try 块里的代码过于庞大,会造成 try 块中出现异常的可能性大大增加,从而导致分析异常原因的难度也大大增加
在同一个 try 块后紧跟大量的 except 块需要分析它们之间的逻辑关系,增加了编程复杂度
正确的做法是把庞大的 try 块分割成多个可能出现异常的程序段落,并把它们放在单独的 try 块中,从而分别捕获并处理异常
不要忽略捕获到的异常
己捕获到异常,应及处理并修复异常
处理异常,对异常进行合适的修复,绕过异常发生的地方继续运行……程序应该尽量修复异常,使程序能恢复运行
重新引发新异常,把在当前运行环境下能做的事情尽量做完,然后进行异常转译,把异常包装成当前层的异常,重新传给上层调用者
如果当前层不清楚如何处理异常,不要在当前层使用 except 语句来捕获该异常,让上层调用者来负责处理该异常