Python 第七章 异常

1.异常概述

异常机制主要依靠try、except、else、finally、raise五个关键字。其中在try后缩进的代码简称try块,里面放置的是可能引发异常的代码;except后对应的是异常类型和一个代码块,用于表明该except块处理这种类型的代码块;在多个except块之后可以放一个else块,表面程序不出现异常时还要执行else块;最后还可以跟一个finally块,用于回收在try块里打开的物理资源,异常机制会保证finally块总被执行;而raise用于引发一个实际的异常,可以单独作为语句使用,引发一个具体的异常对象。

2.使用try。。except捕获异常

try:

​ #业务实现代码

​ ........

except (error1,error2,error3)as e:

​ alert 输入不合法

​ goto retry

如果在执行try块时出现异常,系统自动生成一个异常对象,被交给python解释器,这个过程叫引发异常。

Python解释器收到异常对象时会寻找能处理该异常对象的except块,如果找到合适的except块就交给它处理,这个过程叫捕获异常。

如果找不到捕获异常的except块,则运行环境终止,python解释器将退出。

3.异常类的继承体系

当Python解释器接收到异常对象后会依次判断该异常对象是否是except块后的异常类或其子类的实例(isinstance(ex,Exception1)),如果是,将调用该except块来处理该异常;否则,再次拿该异常对象和下一个except块的异常类进行比较。。

python的所有异常类都从BaseException派生而来,提供给了很多异常类,这些类之间有严格的继承关系。

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模块的argv列表来获取运行python程序时提供的参数。sys.argv[0]通常代表正在运行的python程序名,sys.argv[1]代表运行程序时所提供的第一个参数,sys.argv[2]代表第二个参数。。。

通常把except块放在最后,先处理小异常,再处理大异常。

4.多异常捕获

import sys
try:
    a = int(sys.argv[1])
    print(a)
    b = int(sys.argv[2])
    print(b)
    c = a/b
    print('相除结果:',c)
except (IndexError,ValueError,ArithmeticError):
    print('程序发生了数组越界、数字格式错误、算数异常之一。')
except :
    print('未知异常')

11行只有except关键字,并未指定具体要捕获的类型,它表示可捕获所有类型的异常,一般作为异常捕获的最后一个except块。

5.访问异常信息

如果程序需要在except块中访问异常对象的相关信息,可以通过为异常对象声明变量实现。当python解释器决定调用某个except块来处理该异常对象时,会将异常对象赋值给except块后的异常变量,程序可通过该变量获得异常对象的相关信息。

所有异常对象都包含以下几个常用属性和方法。

  • args:返回异常的错误编号和描述字符串。
  • errno:返回异常的错误编号。
  • strerror:返回异常的描述字符串。
  • with_traceback():处理异常的传播轨迹信息。
def foo():
    try:
        fis = open('a.txt');
    except Exception as e:
        print(e.args)
        print(e.errno)
        print(e.strerror)
foo()
# (2, 'No such file or directory')
2
No such file or directory

r如果要访问异常对象,只要在单个异常类或异常类元组后面加上as再加上异常变量即可。

6.else块

当try块没有出现异常时,会执行else块。一般来说,大部分代码没有else,都直接放在了try后面,但是当try无异常,而else块有异常时就能体现else块的作用。

s = input('输入除数:')
try:
    result = 20 / int(s)
    print('20/%s的结果是:%g' % (s,result))
except ValueError:
    print('值错误,重新输入')
except ArithmeticError:
    print('算术错误,不能输入0')
else:
    print('无异常')

一般来说,大部分代码没有else,都直接放在了try后面,但是当try无异常,而else块有异常时就能体现else块的作用。

def else_test():
    s = input('请输入除数:')
    result = 20 / int(s)
    print('20除以%s的结果是: %g' % (s , result))
def right_main():
    try:
        print('try块的代码,没有异常')
    except:
        print('程序出现异常')
    else:
        # 将else_test放在else块中
        else_test()
def wrong_main():
    try:
        print('try块的代码,没有异常')
        # 将else_test放在try块代码的后面
        else_test()
    except:
        print('程序出现异常')
wrong_main()
right_main()

right_main将else_test()放在else后面;wrong_main放在了try里面。

当try和else块都没有异常时,放在哪都没有区别。

但是如果输入的数据让else_test()出现异常(try块没有异常),此时程序会报错:都输入0,这样都会使else_test出现异常,如果将其放在try后面,出现的异常会被try对应的except捕获。但如果放在else块中,没有except来处理,所以会报错。

所以如果希望异常能被except捕获,就放在try中,如果希望异常能向外传播,就放在else中。

7.使用finally回收资源

有时候程序在try里打开了一些物理资源,比如数据库连接、网络连接、磁盘文件,这些物理资源必须显示回收。

import os
def test():
    fis = None
    try:
        fis = open("a.txt")
    except OSError as e:
        print(e.strerror)
        # return语句强制方法返回
        return        # ①
#        os._exit(1)     # ②
    finally:
        # 关闭磁盘文件,回收资源
        if fis is not None:
            try:
                # 关闭资源
                fis.close()
            except OSError as ioe:
                print(ioe.strerror)
        print("执行finally块里的资源回收!")
test()
#
No such file or directory
执行finally块里的资源回收!

在9行有return,一般情况下,一旦方法执行到return语句,程序会立即结束该方法,现在不会了,会先执行finally里的代码再强制结束方法。

通常情况下,不要在finally块中使用如return或raise等导致方法中止的语句,一旦用了将会导致try块、except块中的return、raise语句失效。

程序在执行try块、except块时遇到了return、raise语句,这两个语句都会使方法立即结束,程序不会立即结束而是会去找该异常处理流程中的finally块,如果没有找到会立即结束方法,找到了立即执行finally,结束后再执行try和except,如果finally中用了return和raise等导致程序中止的语句,finally块就已经终止了方法,不会再去执行try、except。

8.使用raise引发异常

如果需要在程序中自行引发异常,要使用raise,有三种用法:

  • raise:单独一个raise,引发当前上下文中捕获的异常,或默认引发RuntimeError异常。
  • raise 异常类:raise后带一个异常类。引发指定异常类的实例。
  • raise 异常对象:引发指定的异常对象。

上面三种用法最终都要引发一个异常实例,raise每次引发一个实例。

import traceback
def main():
    try:
        # 使用try...except来捕捉异常
        # 此时即使程序出现异常,也不会传播给main函数
        mtd(3)
    except Exception as e:
        print('程序出现异常:', e)
#        help(e.with_traceback)
        traceback.print_exc()
#        e.with_traceback(e)
    # 不使用try...except捕捉异常,异常会传播出来导致程序中止
    mtd(3)
def mtd(a):
    if a > 0:
        raise ValueError("a的值大于0,不符合要求")
main()

程序在调用mtd(3)时有异常,被except捕获,第二次调用mtd(3)时没有被except捕获,会一直向上传播被python解释器终止程序。

9.except和raise同时使用

为了实现通过多个方法协作处理同一个异常的情形,可以在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("转换出异常:", e)         
            # 再次引发自定义异常
#            raise AuctionException("竞拍价必须是数值,不能包含其他字符!")  # ①
            raise AuctionException(e)
        if self.init_price > d:
            raise AuctionException("竞拍价比起拍价低,不允许竞拍!")
        initPrice = d
def main():
    at = AuctionTest(20.4)
    try:
        at.bid("df")
    except AuctionException as ae:
        # 再次捕获到bid()方法中的异常,并对该异常进行处理
        print('main函数捕捉的异常:', ae)
main()
转换出异常: could not convert string to float: 'df'
main函数捕捉的异常: could not convert string to float: 'df'

except捕获到异常后,系统打印了该异常的字符串信息,接着引发一个AuctionException异常,通知该方法的调用者处理该AE异常。14行就是把原始异常e包装成了AE异常,这种方式也叫异常包装;14行也可以不用参数,只用一个raise,会再次引发except捕获的异常。

10.异常传播轨迹

异常对象提供了一个with_traceback用于处理异常的传播轨迹,查看异常的传播轨迹可追踪异常触发的源头,也可看到异常一路触发的轨迹。

class SelfException(Exception): pass

def main():
    firstMethod()
def firstMethod():
    secondMethod()
def secondMethod():
    thirdMethod()
def thirdMethod():
    raise SelfException("自定义异常信息")
main()
Traceback (most recent call last):
  File "D:/python_work/GUI/基础.py", line 11, in <module>
    main()
  File "D:/python_work/GUI/基础.py", line 4, in main
    firstMethod()
  File "D:/python_work/GUI/基础.py", line 6, in firstMethod
    secondMethod()
  File "D:/python_work/GUI/基础.py", line 8, in secondMethod
    thirdMethod()
  File "D:/python_work/GUI/基础.py", line 10, in thirdMethod
    raise SelfException("自定义异常信息")
__main__.SelfException: 自定义异常信息

从最下面一行往上触发。

traceback模块可以处理异常传播轨迹。

# 导入trackback模块
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'))
Traceback (most recent call last):
  File "D:/python_work/GUI/基础.py", line 14, in <module>
    main()
  File "D:/python_work/GUI/基础.py", line 6, in main
    firstMethod()
  File "D:/python_work/GUI/基础.py", line 8, in firstMethod
    secondMethod()
  File "D:/python_work/GUI/基础.py", line 10, in secondMethod
    thirdMethod()
  File "D:/python_work/GUI/基础.py", line 12, in thirdMethod
    raise SelfException("自定义异常信息")
SelfException: 自定义异常信息

11.异常处理规则

  • 不要过度使用异常
  • 不要使用过于庞大的try块
  • 不要忽略捕获到的异常
posted @ 2021-03-24 21:33  KKKyrie  阅读(174)  评论(0)    收藏  举报