7. python异常处理

一、 异常处理机制

  • 异常处理使得程序具有极好的容错性,让程序更加健壮。
  • 分离了“业务实现”和“错误处理”代码,提供了更好的程序可读性。
  • 异常处理机制主要依赖try,except,else,finally和raise五个关键字。
  • 异常处理可以嵌套,但通常不建议超过2层。嵌套过多可能导致程序性能下降,同时降低了可读性。

1. try...except...

#伪代码
try:
    #业务实现代码
    ...
except Error type1:
    #异常处理1
except Error type12:
    #异常处理2

引发异常过程:若执行try块中业务代码出现了异常,系统自动生成一个异常对象,异常对象会被提交给python解释器。

捕获异常过程:python解释器收到异常对象时,会依次寻找能处理该异常对象的except块(最可能的放最前面)。若找到匹配的,则将该异常对象交给该except块处理,若找不到则报错。

python常见异常类继承关系及异常关系参考

https://docs.python.org/3/library/exceptions.html#exception-hierarchy

inputStr=input("请输入除数:")
try:
    result=20/int(inputStr)
    print("20除以%s的结果为:%g" % (inputStr,result))
except ValueError:
    print("输入非数值,请输入一个非0数字")
except ArithmeticError:
    print("输入值为0,请输入一个非0数字")

#测试结果
请输入除数:dsads200
输入非数值,请输入一个非0数字

请输入除数:0
输入值为0,请输入一个非0数字

请输入除数:100
20除以100的结果为:0.2

2. 多异常捕获

可以使用一个except块捕获多种类型异常,其实就是构建多个异常类的元组,但这种报错可能过于笼统。

inputStr=input("请输入除数:")
try:
    result=20/int(inputStr)
    print("20除以%s的结果为:%g" % (inputStr,result))
except (ValueError,ArithmeticError):
    print("输入了非数值或0,请输入一个非0数字")

#测试结果

请输入除数:kkk
输入了非数值或0,请输入一个非0数字

请输入除数:0
输入了非数值或0,请输入一个非0数字

请输入除数:30
20除以30的结果为:0.666667

3. 访问异常信息

python会将异常对象赋值给except块后的异常变量,程序可通过这些变量访问对象信息

inputStr=input("请输入除数:")
try:
    result=20/int(inputStr)
    print("20除以%s的结果为:%g" % (inputStr,result))
except Exception as e:
    print("输入了非数值或0,请输入一个非0数字")
    #异常错误号和详细信息
    print(e.args)

#输出结果
请输入除数:0
输入了非数值或0,请输入一个非0数字
('division by zero',)

4. else块

异常处理流程中还可添加else块,当try块中没有出现异常时,程序会执行else块中内容。(可以但没必要,没有异常通常直接放在try后执行就可以了)

inputStr=input("请输入除数:")
try:
    result=20/int(inputStr)
    print("20除以%s的结果为:%g" % (inputStr,result))
except Exception as e:
    print("输入了非数值或0,请输入一个非0数字")
else:
    print("未发生异常")

#输出结果
请输入除数:100
20除以100的结果为:0.2
未发生异常

5. 使用finally回收资源

  • 除非在程序中使用os._exit(1)强制退出python编译器,否则无论是否发生异常,也无论异常在哪个except块被捕获(包括在try或except中有return语句),finally块总会被执行。
  • finally块必须位于所有的except块后
  • 通常情况下,不要在finally块中使用return,raise等导致方法中止的语句,它会让try和except块中的return,raise语句失效(因为无论前面返回什么都会被finally块的return覆盖)
import os
def test():
    fis=None
    try:
        fis=open("a.txt")
    except OSError as e:
        #返回详细报错信息
        print(e.strerror)
        #测试return返回,仍会执行finally部分
        return
        #删除下面注释,os._exit(1)会直接退出python编译器,不执行finally部分
        #os._exit(1)
    finally:
        #关闭磁盘文件,回收资源
        if fis is not None:
            try:
                #关闭资源
                fis.close()
            except OSError as ioe:
                print(ioe.strerror)
        print("执行finally块中的资源回收")

#测试return,输出
No such file or directory
执行finally块中的资源回收

#测试os._exit(1),输出
No such file or directory

二、 使用raise抛出自定义异常

异常其实是很主观的东西,除了明确会导致程序错误的报错外,也需要结合具体业务确定。例如设置密码时小于6位抛出异常,这就不是python默认的报错,属于自定义异常。

 

1. raise语句

raise语句用于在程序中抛出自定义异常,通常有以下3种用法:

raise # 单独一个raise,抛出当前上下文中捕获的异常(例如except语句中),或默认抛出RuntimeError异常

raise 异常类 # 抛出指定异常类的默认实例

raise 异常对象 # 抛出指定异常对象

自定义异常类

自定义异常类都应该继承Exception类或其子类,定义时通常指定其父类即可。

class MyException(Exception):
    pass

3种用法最终抛出的都是异常实例,raise语句每次只能抛出一个异常实例,若未在except中捕获,程序会直接报错退出

inputStr=input("请输入除数:")
try:
    num=int(inputStr)
    #若num>20则抛出自定义异常
    if num>20:
        #引发默认RuntimeError异常
        raise
    result=20/num
    print("20除以%s的结果为:%g" % (inputStr,result))
except ValueError:
    print("输入非数值,请输入一个非0数字")
except ArithmeticError:
    print("输入值为0,请输入一个非0数字")
except RuntimeError:
    print("num>20,抛出了自定义RuntimeError异常")

#测试输出
请输入除数:21
num>20,抛出了自定义RuntimeError异常

2. except与raise结合使用

实际应用中,当一个异常出现时,可能无法单靠某个方法进行处理,而要由几个方法协作,此时可以通过except与raise结合实现。一个常见的例子是,应用一方面需将报错记入日志文件,另一方面还需返回其他告知用户报错信息。

class MyException(Exception):
    pass

class Bid:
    #构造方法定义起拍价
    def __init__(self,init_price):
        self.init_price=init_price
    #定义竞拍函数
    def bid_func(self,bid_price):
        d=0.0
        try:
            d=float(bid_price)
        except Exception as e:
            #此处只打印简单异常信息
            print("类型转换出现异常",e)
            #再次抛出自定义异常进行详细处理
            raise MyException("竞拍价必须为数值")

        if self.init_price>d:
            raise MyException("竞拍价不得低于起拍价")
        initPrice=d

def main():
    test=Bid(21.6)
    try:
        test.bid_func("yyy")
        test.bid_func(6)
    except MyException as e2:
        #再次捕获异常并进行处理
        print("main函数中捕获的异常",e2)

main()

#测试输出
类型转换出现异常 could not convert string to float: 'yyy'
main函数中捕获的异常 竞拍价必须为数值

三、 异常传播轨迹

python提供了traceback模块处理异常传播轨迹,查看异常源头。

traceback模块提供了两个常用方法:

  • format_exc():将异常传播轨迹转换为字符串
  • traceback.print_exc():将异常传播轨迹输出到控制台或指定文件

print_exc()的完整形式是print_exception(etype,value,tb[,limit[,file]])

  • etype:指定异常类型
  • value:指定异常值
  • tb:指定异常traceback信息
  • limit:限制显示异常的传播层数,若不设置默认全部显示
  • file:将异常传播轨迹输出到指定文件,若不设置则输出到控制台
import traceback

class MyException(Exception):
    pass

def main():
    first()

def first():
    second()

def second():
    third()

def third():
    raise MyException("自定义异常信息")

try:
    main()
except:
    #捕获异常并输出至控制台
    traceback.print_exc()
    #捕获异常并输出至指定文件
    traceback.print_exc(file=open('log.txt','a'))

#输出如下
Traceback (most recent call last):
  File "E:/pytest02/var.py", line 19, in <module>
    main()
  File "E:/pytest02/var.py", line 7, in main
    first()
  File "E:/pytest02/var.py", line 10, in first
    second()
  File "E:/pytest02/var.py", line 13, in second
    third()
  File "E:/pytest02/var.py", line 16, in third
    raise MyException("自定义异常信息")
MyException: 自定义异常信息

输出至文件结果

报错栈应该从下往上看,报错源头是在third方法第16行,对应的正好是抛出异常部分

若不使用该模块,也不捕获异常,python编辑器会利用自带的with_traceback在控制台打出报错栈。

class MyException(Exception):
    pass

def main():
    first()
def first():
    second()
def second():
    third()
def third():
    raise MyException("自定义异常信息")

main()

#控制台报错信息

Traceback (most recent call last):
  File "E:/pytest02/var.py", line 16, in <module>
    main()
  File "E:/pytest02/var.py", line 5, in main
    first()
  File "E:/pytest02/var.py", line 8, in first
    second()
  File "E:/pytest02/var.py", line 11, in second
    third()
  File "E:/pytest02/var.py", line 14, in third
    raise MyException("自定义异常信息")
__main__.MyException: 自定义异常信息

四、 异常处理规则

  • 不要过度使用异常
  • 不要使用过于庞大的try块
  • 不要忽略捕获到的异常
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hehuyi_In

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值