异常处理的语法与继承结构

要点概论:

1. 使用 try , except

2. 异常继承结构

3.引发(raise)异常

4.python 的异常风格

5. 认识堆栈追踪

6. 提出警告信息(待补充)

7.附录(完整的异常继承结构,traceback模块官方文档)

1. 使用 try ,except:

  先看一段简单的代码:

try:
    num = int(input('请输入数字>>>'))
    print(num)
except ValueError:
    print('输入错误')

  在这段代码中,python解释器尝试执行 try 区块中的程序代码,如果发生异常,执行流程会跳离异常发生点,然后对比 except 声明的类型,如果符合,则执行 except 区块中的程序代码,

  执行完毕后若仍有后续的程序执行流程,就会继续执行。如果没有相符的异常类型,或者异常完全没有使用 try , except 处理,那么异常就会持续往上层调用者传递,在每一层调用处中断,

  直到最顶层由 python 解释器来处理(后面会由此衍生出 Traceback 模块)。

  

  PS:在 python 中,异常并不一定时错误,例如使用 for in 语句时,其实底层就运用到了异常处理机制(for in 会在遇到 StopIteration 时结束迭代,因此我们不会看到 StopIteration )。

2. 异常继承结构

  如果一个异常在 except 的对比过程中就符合了某个异常的父类型,后续即使定义了 except 对比子类型异常,也等同于没有定义。例如:

try:
    dividend = int(input('输入被除数:'))
    divisor = int(input('输入除数:'))
    print('{} / {} = {}'.format(dividend,divisor,dividend / divisor))
except ArithmeticError:
    print('运算错误')
except ZeroDivisionError:
    print('除零错误')
View Code

  在上面这段代码中,永远也不会看到 '除零错误' 的信息,因为在异常继承结构中,ArithmeticError 是 ZeroDivisionError 的父类。发生 ZeroDivisionError 时,程序在 except 对比时会先遇到 ArithmeticError ,因此就执行了对应的区块。

  

  PS:在 python 中,异常都是 BaseException 的子类,当使用 except 而没有指定异常类型时,实际上都是对比 BaseException。

 

3. 引发(raise)异常(待补充raise from,__cause__,__context__相关内容

  先看一个简单的类(描述了一个存取款的简单银行业务):

class Account:
    def __init__(self,name,number,balance):
        self.name = name
        self.number = number
        self.balance = balance

    def deposit(self,amount):
        if amount <= 0:
            print('存款金额不能为负')
        else:
            self.balance += amount
    
    def withdraw(self,amount):
        if amount > self.balance:
            print('金额不足')
        else:
            self.balance -= amount
    
    def __str__(self):
        return "Account('{name}','{number}',{balance})".format(
            name = self.name,number = self.number,balance = self.balance
        )
Account

    可以看到,当存款金额为负或者余额不足时,会使得相关流程无法继续而中断,

    如果想让调用者知道因为某些原因而使得流程无法继续而必须中断时,可以引发异常。

  在 python 中如果想要引发异常,可以使用 raise ,之后指定要引发的异常对象或类型,只指定异常类型的时候会自动创建异常对象。例如:

class Account:
    def __init__(self,name,number,balance):
        self.name = name
        self.number = number
        self.balance = balance

    def check_amount(self,amount):      #检查参数值
        if amount <= 0:
            raise ValueError('金额必须时正数:' + str(amount))

    def deposit(self,amount):
        self.check_amount(amount)

        self.balance += amount

    def withdraw(self,amount):
        self.check_amount(amount)

        if amount > self.balance:
            raise BankingException('余额不足')  #自定义异常

        self.balance -= amount

    def __str__(self):
        return "Account('{name}','{number}',{balance})".format(
            name = self.name,number = self.number,balance = self.balance
        )
    
class BankingException(Exception):  #自定义异常
    def __init__(self,message):
        super().__init__(message)
exceptions Account

  例子中定义了一个 check_amount() 方法用来检查传入的金额是否为负。

  余额不足部分自定义了一个异常 BankException 。

  【可以为自己的 API 创建一个根异常,业务相关的异常都可以衍生自这个根异常,这样便于使用 API 的用户在 except 时使用根异常来处理 API 相关的异常。】

  

4. python 异常风格

  在 python 中异常并不一定时错误,例如 KeyboardInterrupt ,SystemError, GeneratorExit ,或者时 StopIteration 等更像是一种事件,代表着流程因为某个原因无法继续而必须中断。

  【对于标准链接库会引发的异常,也可以从开发者的角度来思考,为什么他想要引发这样的异常?如此就可以知道该怎么处理异常,例如留下日志信息,转为其他流程或重新引发异常。】

  

  在其他程序设计语言中常会有个告诫,异常处理就应当用来处理错误,不应该将异常处理当成是程序流程的一部分。

  然而在 python 中,就算异常是个错误,只要程序代码能明确表达出意图,也常被当成流程的一部分。

  举个例子来说,如果 import 的模块不存在,就会引发 ImportError 错误。然而 import 是条语句,可以出现在语句能出现的场合,因此有时候会想看看某个模块能否被 import,

  若模块不存在,则改为 import 另一个模块,此时在 python 中就会如此编写:

try:
    import some_module
except ImportError:
    import other_module

  又或者通过另一种方式完成:

import importlib

some_loader = importlib.find_loader('some_module')
if some_loader:
    import some_module
else:
    import other_module

  如果指定的模块确实存在,那么 importlib.find_loader() 的返回值就不会是 None,因此可以 import指定模块而不会引发 ImportError 。

  一般情况下用 try,except 语句处理比较好。

5. 认识堆栈追踪

  在 API 的多重调用下,异常发生点可能在某个函数或方法之中,若想得知异常发生的根源,以及多重调用下异常的传递过程,可以使用 traceback 模块

  这个模块提供了一些方式,模拟了 python 解释器在处理异常堆栈追踪(Stack Trace)时的行为,可在受控制的情况下获取,格式化或显示异常堆栈追踪信息。

  1)使用 traceback.print_exc() 

    查看堆栈追踪最简单的方法就是直接调用 traceback 模块的 print_exc() 函数。例如:

def a():
    text = None
    return text.upper()
def b():
    a()
def c():
    b()

try:
    c()
except:
    import traceback
    traceback.print_exc()
traceback.print_exc()

    在这个例子中,c() 函数调用 b() 函数,b()  函数调用  a()  函数 ,a() 函数中会因 text 为 None,之后试图调用 upper() 而引发 AttributeError 错误。

    假设事先并不知道这个调用顺序(比如说在使用一个链接库),当异常发生而被对比后,可以调用  traceback.print_exc() 显示堆栈追踪(在  Pycharm IDE 中,按下行数就会直接打开源码文件并跳至所对应的行数)。

    

     traceback.print_exc() 还可以指定 file 参数,指定一个已打开的文件对象,将堆栈追踪信息输出到文件里。例如 traceback.print_exc(file=open('traceback.txt','w+')) 可将堆栈信息写到 traceback.txt 中。

    traceback.print_exc() 的 limit 参数默认时 None ,也就是不限制堆栈追踪个数,可以指定为正数或负数。正数代表显示最后几次的堆栈追踪个数,负数代表显示最初几次的堆栈追踪个数。

    traceback.print_exc() 的 chain 参数默认时 True ,也就是一并显示 __cause__,__context__等串连起来的异常。

    如果只想获取堆栈追踪的字符串描述,可以使用 traceback.format_exc() ,它会返回字符串,只具有 limit 与 chain 两个参数。

  

  2)使用 sys.exc_info() 

    sys.exc_info()  可获取一个元组对象,当中包括了异常的类型,实例以及 traceback 对象。例如:

import sys
try:
    raise Exception('shit happens')
except:
    print(sys.exc_info())


#(<class 'Exception'>, Exception('shit happens',), <traceback object at 0x000001F41EF3EA48>)
sys.exc_info()

     traceback 对象代表了调用堆栈中每一个层次的追踪,可以使用 tb_next 获取更深一层的调用堆栈。例如:

import sys

def test():
    raise Exception('shit happens')

try:
    test()
except:
    type,value,traceback = sys.exc_info()
    print('异常类型',type)
    print('异常对象',value)

    while traceback:
        print('-------')
        code = traceback.tb_frame.f_code
        print('文件名:',code.co_filename)
        print('函数或模块名称:',code.co_name)
        
        traceback = traceback.tb_next

输出结果如下:

异常类型 <class 'Exception'>
异常对象 shit happens
-------
文件名: C:/Users/Administrator/PycharmProjects/S9/练习/异常处理/初探堆栈追踪.py
函数或模块名称: <module>
-------
文件名: C:/Users/Administrator/PycharmProjects/S9/练习/异常处理/初探堆栈追踪.py
函数或模块名称: test
View Code

    tb_frame 代表了该层追踪的所有对象信息, f_code 可以获取该层的程序代码信息,例如 co_name 可获取函数或模块名称,co_filename 则表示该程序代码所在的文件。

    也可以通过 tb_frame 的 f_locals 和 f_globals 来获取执行时的局部变量或全局变量,返回的是个字典对象。

  3)使用 sys.excepthook()

      对于一个未匹配到的异常, python 解释器最后会调用 sys.excepthook() 并传入三个自变量:异常类,实例与 traceback 对象,也就是 sys.exc_info() 返回的元组中的三个对象。

      默认的操作时显示相关的异常追踪信息(也就是程序结束前看到的那些信息)。

      

      如果想要自定义  sys.excepthook() 被调用时的行为(或操作),也可以自行指定一个可接受三个自变量的函数给  sys.excepthook ,如果希望对比程序中没有被对比到的其他全部异常,

      就可以使用这个特性,而不一定要在程序最顶层使用 try ,except。例如:

import sys

def my_excepthook(type,value,traceback):
    print('异常类型:',type)
    print('异常对象:',value)

    while traceback:
        print('------')
        code = traceback.tb_frame.f_code
        print('文件名:',code.co_filename)
        print('函数或模块名称:',code.co_name)

        traceback = traceback.tb_next

sys.excepthook = my_excepthook

def test():
    raise Exception('shit happens')

test()
注册 sys.excepthook() 的方式实现

      执行结果与上一个例子相同,不过这次采取的是注册 sys.excepthook() 的方式。

6. 提出警告信息(待补充)

  

7. 附录:

  traceback官方帮助文档:https://docs.python.org/3.5/library/traceback.html

  python中完整的异常继承结构:https://docs.python.org/3.5/library/exceptions.html

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
      |    +-- FloatingPointError
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
      +-- LookupError
      |    +-- IndexError
      |    +-- KeyError
      +-- MemoryError
      +-- NameError
      |    +-- UnboundLocalError
      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
      |    +-- NotImplementedError
      |    +-- RecursionError
      +-- SyntaxError
      |    +-- IndentationError
      |         +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
      |    +-- UnicodeError
      |         +-- UnicodeDecodeError
      |         +-- UnicodeEncodeError
      |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- ResourceWarning
异常继承结构

  KeyboardInterrupt 表示发生了一个键盘中断;SystemError 则是由 sys.exit() 引发的异常,表示离开 python 程序;

  GeneratorExit 会在生成器的 close() 方法被调用时从当时暂停的位置引发,如果在定义生成器时想要在 close() 时为生成器做些资源善后等操作,就可以使用。例如:(待验证,pycharm 里运行结果不一致

def natural():
    n = 0
    try:
        while True:
            n += 1
            yield n
    except GeneratorExit:
        print('GeneratorExit',n)

n = natural()

print(next(n))    # 1
n.close()         # GeneratorExit 1
View Code

  KeyboardInterrupt ,SystemError, GeneratorExit 都直接继承了 BaseException,这是因为它们在 python 中都属于退出系统意外的异常。

  如果想要自定义异常,不要直接继承 BaseException ,而应该继承 Exception,或者是 Exception 的相关子类来继承。

  

  PS:在继承 Exception 自定义异常时,如果定义了 __init__() , 建议将自定义的 __init__()传入的自变量通过 super().__init__(arg1,arg2,...) 来调用 Exception 的 __init__(),

  因为 Exception 的 __init__()默认接收所有传入的自变量,而这些被接受的全部自变量可通过 args 属性以一个元组获取。

原文地址:https://www.cnblogs.com/HZY258/p/8461674.html