疯狂python讲义学习日志06——异常处理
引言
异常机制是一门编程语言是否成熟的标准,python提供了成熟的异常机制。异常机制可以使程序中的异常处理代码和正常业务代码分离,保重程序代码更加优雅,并且可以提高程序的健壮性。
python的异常机制主要依赖try、except、else、finally和raise五个关键字,其中在try中放置可能出现问题的代码;在except后对应的是异常类型和一个处理异常的代码块;在多个except之后可以放一个else,表明程序不出现异常时还要处理else块(这是本章一个小难点);最后跟一个finally块,其中放置回收在try里面打开资源的代码,异常机制会保证finally块总会被执行(使用时有一个注意点);raise用于手动引发一个异常,可以单独使用,引发一个异常。
1 异常处理机制
1.1 使用try…except处理异常
下面是示例代码:
try:
#业务实现代码
...
except (Error1,Error2,...) as e:
alert 输入不合法
goto retry
1、如果在执行try里的业务逻辑代码时出现异常,系统自动生成一个异常对象,该异常对象被提交给python解释器,这个过程被称为引发异常。python解释器收到异常之后,将异常交给能处理该类型异常的except块,这个过程称为异常捕获。
2、不管代码是否处于try块中,甚至包括except块中的代码,只要执行出现了异常,系统总会生成一个Error对象。如果程序没有为这段代码定义任何的except块,则python解释器无法找到处理该异常的except块,程序就此退出。
1.2 异常类的继承体系
1、python所有异常都从BaseException继承而来,提供了丰富的异常类,这些异常之间有着严格的继承关系。
2、如果用户要实现字定义异常,则不应该继承BaseException,而是应该继承Exception类,如何使用会在后面放出示例代码。
3、在进行异常捕获时不仅要把Exception类对应的except块放在最后,而且所有父类异常的except块都应该放在子类异常的except块后面。(总结来说就是一定要先处理小异常,再处理大异常)
1.3 多异常捕获
使用一个except块时只要将多个异常类用圆括号括起来(异常元组),就可以用一个except实现多异常捕获。
import sys
try:
a = int(sys.argv[1])
b = int(sys.argv[2])
c = a / b
print("您输入的两个数相除的结果是:",c)
except(IndentationError,ValueError,ArithmeticError):
print("程序发生了数组越界、数字格式异常、算术异常之一")
except:
print("未知异常")#省略异常类合法,表示可以捕获所有的异常类型,一般作为最后一个except块
1.4 访问异常信息
python可以通过为异常对象声明变量并访问异常的信息,python可以访问到的异常对象相关信息如下:
1)、args:返回异常的错误编号和描述字符串;
2)、errno:返回异常的错误编号;
3)、strerror:返回异常的描述字符串;
4)、with_traceback():返回异常的传播轨迹(很重要,后面将展开);
def foo():
try:
fis = open("a.txt")
except Exception as e:
#访问异常的错误编号和详细信息
print(e.args)
#访问异常的错误编号
print(e.errno)
#访问异常描述信息
print(e.strerror)
foo()
1.5 else块
1、示例代码如下:
s = input("请输入除数:")
try:
result = 20 / int(s)
print("20除以%s的结果是:%g" % (s,result))
except ValueError:
print("值错误,您必须输入数值")
except ArithmeticError:
print("算术错误,您不能输入0")
else:
print("没有出现异常")#这里应该产生一个疑问,md这个else好像没有什么实际意义啊?!答:问的好,继续往下看。
2、当try块没有异常,else块有异常时就会显现其作用,放在else块中代码引发的异常不会被except块捕获。如果希望异常被except捕获就将代码放在try块中,如果希望异常能够向外传播(不被捕获),应该将其放在else块中。
def else_test():
s = input("请输入除数:")
result = 20 / int(s)
print("20除以%s的结果是:%g" % (s, result))
def rignt_main():
try:
print("try代码块没有异常")
except:
print("程序出现异常")
else:
else_test()
def wrong_main():
try:
print("try代码块没有异常")
else_test()
except:
print("程序出现异常")
rignt_main()
wrong_main()
1.6 使用finally回收资源
1、python的垃圾回收不会回收任何物理资源(打开文件句柄等),只能回收堆栈中对象占用的内存。
2、使用finally可以处理在try块中打开的物理资源。
3、异常处理中,只有try是必须的,except和finally都是可选的,但是except和finally至少应该出现一个。finally必须放在所有except的最后。
import os
def test():
fis = None
try:
fis = open("a.txt")
except OSError as e:
print(e.strerror)
return #语句强制返回,即便如此返回之前也会调用执行finally代码块
#os.exit(0) 直接退出,不会执行finally代码块
finally:
if fis is not None:
try:
fis.close()
except OSError as ioe:
print(ioe.strerror)
print("执行finally回收资源")
test()
4、除非在finally块中使用return或raise导致方法中止的语句(所以不要在finally中添加return 或raise异常),否则不管try和except块中执行怎样的代码,finally都会被执行。
5、python可以通过嵌套处理异常,但是通常没有必要使用超过两层的嵌套异常处理。
2 使用raise处理异常
2.1 引发异常
1、可以使用raise在程序中手动引发异常(每次只能引发一个异常实例):
1)raise:单独一个raise,引发当前上文 和 下文中捕获的异常。
2)raise 异常类:引发指定异常类的默认实例。
3)raise 异常对象:引发指定的异常对象。
2、python解释器对异常的处理没有任何差别,用户引发的异常也可以使用try…except处理,如果不管它,让该异常向上(调用者传播),如果异常传到Python解释器,程序就会中止。
2.2 自定义异常类
1、直接上示例代码,不需要书写更多的代码,直接通过异常类的名称表示异常类型。
class AuctionException(Exception): pass
2.3 except和raise同时使用
1、使用except和raise结合处理异常的方法最为常用,实际应用对异常的处理一般分为两部分:(1)应用后台通过日志记录发生异常的详细情况;(2)应用向使用者传达某种指示;在这种情况下,需要将except和raise结合使用。
class AuctionException(Exception): pass
class AuctionTest:
def __init__(self,init_price):
self.init_price = init_price
def bid(self,bid_price):
d = 0.0
try:
d = float(bid_price)
except Exception as e:
print("转换异常")
raise AuctionException ("竞拍价必须是数值,不能包含其他字符")
if self.init_price > d:
raise AuctionException("竞拍价比起拍低,不允许拍卖")
initPrice = d
def main():
at = AuctionTest(20.4)
try:
at.bid("df")
except AuctionException as e:
print("main函数捕获的异常:",e)
main()
2、raise可以不需要参数,这种情况下直接将原始异常不经过转译直接传给调用者。
class AuctionException(Exception): pass
class AuctionTest:
def __init__(self,init_price):
self.init_price = init_price
def bid(self,bid_price):
d = 0.0
try:
d = float(bid_price)
except Exception as e:
print("转换异常")
raise
if self.init_price > d:
raise AuctionException("竞拍价比起拍低,不允许拍卖")
initPrice = d
def main():
at = AuctionTest(20.4)
try:
at.bid("df")
except AuctionException as e:
print("main函数捕获的异常:",type(e))
main()
3.python的异常传播轨迹
1、python提供了一个专门的with_traceback用于处理异常的传播轨迹,查看异常的传播轨迹。
class SelfException(Exception):pass
def main():
firstMethod()
def firstMethod():
secondMethod()
def secondMethod():
thirdMethod()
def thirdMethod():
raise SelfException("自定义异常信息")
main()
2、如果异常没有完全被捕获(包括异常没有被捕获或者异常被处理后重新引发异常),异常传播到python解释器,python解释器就会中止该程序,并打印异常的传播信息。
3、python提供了traceback模块来处理异常传播轨迹。
1)traceback.print_exec():将异常传播轨迹信息输出到控制台或指定文件中。
完整形式为print_exec(etype,value,tb[limit[,file]]),limit:用于限制显示异常传播的层数,file:指定将异常传播轨迹信息传播输出到指定文件中。
1))etype:指定异常类型
2))value:指定异常值
3))tb:指定异常的traceback信息
2)format_exec():将异常传播轨迹信息转换为字符串。
4、当程序处于except块中,该except块捕获的异常信息可以通过sys对象获取,其中sys.exc_type、sys.exc_value、sys.exc_traceback就代表当前except块内的异常类型、异常值及异常传播轨迹。
import traceback
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')
4. 异常处理规则
成功处理异常应该实现4个目标:
1)使程序代码最小化
2)捕获并保留诊断信息
3)通知合适的人员
4)采用合适的方式结束异常活动
4.1不要过度使用异常
1、对于完全已知的错误和普通的错误,应该编写处理这种错误的代码。对于外部的、不能确定的错误和允许时预知的错误才能使用异常。
2、异常处理的初衷是将不可预期异常的处理代码和正常的业务逻辑处理代码分离,而且异常机制的效率比正常的流程控制效率差,所以不要使用异常处理代替正常的程序流程控制。
3、不要使用归于庞大的try块,应该分割成多个可能出现异常的程序段落,并把它们单独放在try块中。
4.2不要忽略异常
通常建议对异常采用适当的措施:
1)处理异常:对异常进行合适的修复,然后绕过异常发生的地方继续运行。
2)重新引发新异常:把异常进行转移并传播给调用者。
3)在合适的层处理异常:如果当前层不知道如何处理,就不要在当前层处理,让调用者负责处理该异常。