python的异常处理以及调用栈

1 异常类型

基本的异常,由于代码执行的预期内和预期外的错误抛出的异常

  • 异常基本上是在执行程序时触发一些预期内的错误,而异常处理,就是我们对这些问题做出相应处理,防止程序中断并给出友好提示和应对方案。
    例如:
def func(a,b):
    print(a/b)
    print('执行成功')
func(1,0)

代码执行后会抛出错误ZeroDivisionError: division by zero,且print('执行成功')不会执行,抛出的错误就是一种异常,而我们要做的就是给出相应的处理,使其给出错误但是不会导致程序中断而影响后续代码的执行。

raise 主动抛出异常

例如

def func(a,b):
    if b == 0:
        # raise Exception('分母不能为0')
        raise ZeroDivisionError(('分母不能为0'))
    print(a/b)
    print('执行成功')
func(1,0)

assert 断言异常,本质上属于主动抛出异常

例如

def func(a,b):
    assert b != 0
    print(a/b)
    print('执行成功')
func(1,0)

2 异常的捕获和处理

if处理异常

如上代码,在可知范围内,可以提前检查a,b,可能会变成这样

def func(a: int, b: int):
    if not isinstance(a, int):
        print(f'{a}输入有误,请输入整数')
    elif not isinstance(b,int):
        print(f'{b}输入有误,请输入整数')
    elif b == 0:
        print(f'输入有误,分母不能为0')
    else:
        print(a/b)
    print('执行成功')
func(1,0)

可以看出if判断式的异常处理只能针对某一段代码,对于不同的代码段的相同类型的错误你需要写重复的if来进行处理。而且在你的程序中频繁的写与程序本身无关,与异常处理有关的if,会使得你的代码可读性极其的差而不容易看出真正的业务核心代码。并不推荐使。

正确的处理方式 try...except...else...finally...

完整结构如下

try:
    # 可能发生异常的代码 
    body
# 如果有异常向try抛出,则会依次搜索各条except子句,查找关联的异常类型与抛出的异常匹配的子句。
except 异常类型1 as 变量名: # 变量名存储的是具体的错误信息
    code 
    此处处理对应异常1的逻辑
except 异常类型2 as 变量名:  # 变量名存储的是具体的错误信息
    code 
    此处处理对应异常2的逻辑
except Exception as 变量名:  # 变量名存储的是具体的错误信息
    code 
    此处处理对应其他异常的逻辑
else:
    code 
    如果以上代码没有发生异常以及异常处理工作就执行这里的代码 
finally:
    不管代码是否有异常都会执行,且在函数中遇到return仍然会执行,一般情况下用于这个函数中资源的回收
  • 关于最后一个except的异常类型:Exception,万能的异常,它可以捕获任意异常。需要视情况使用,当无论出现什么异常,都使用同一段代码逻辑去处理时,那么只有一个Exception就足够了否则就需要用到多分支了。一般来说是使用多分支+万能异常来处理异常。使用多分支优先处理一些能预料到的错误类型,一些预料不到的错误类型应该被最终的万能异常捕获。需要注意的是,万能异常一定要放在最后,否则就没有意义了。

3 拓展

内置异常

python中,所有异常必须为一个派生自BaseException的类的实例。内置异常类可以被子类化以定义新的异常;当需要自定义异常时,建议从Exception类或它的某个子类而不是从BaseException来派生新的异常。

BaseException ————所有内置异常的基类
 +-- SystemExit ————此异常由 sys.exit() 函数引发。 它继承自 BaseException 而不是 Exception 以确保不会被处理 Exception 的代码意外捕获。
 +-- KeyboardInterrupt ————当用户按下中断键 (通常为 Control-C 或 Delete) 时将被引发。
 +-- GeneratorExit ————当一个 generator 或 coroutine 被关闭时将被引发
 +-- Exception ————所有内置的非系统退出类异常都派生自此类。 所有用户自定义异常也应当派生自此类。
      +-- StopIteration ————由内置函数 next() 和 iterator 的 __next__() 方法所引发,用来表示该迭代器不能产生下一项。
      +-- StopAsyncIteration ————必须由一个 asynchronous iterator 对象的 __anext__() 方法来引发以停止迭代操作。
      +-- ArithmeticError ————此基类用于派生针对各种算术类错误而引发的内置异常
      |    +-- FloatingPointError
      |    +-- OverflowError ————当算术运算的结果大到无法表示时将被引发。
      |    +-- ZeroDivisionError ————当除法或取余运算的第二个参数为零时将被引发。
      +-- AssertionError ————当 assert 语句失败时将被引发。
      +-- AttributeError ————当属性引用或赋值失败时将被引发
      +-- BufferError ————当与 缓冲区 相关的操作无法执行时将被引发。
      +-- EOFError ————当 input() 函数未读取任何数据即达到文件结束条件 (EOF) 时将被引发
      +-- ImportError ————当 import 语句尝试加载模块遇到麻烦时将被引发。
      |    +-- ModuleNotFoundError ————当一个模块无法被定位时将由 import 引发
      +-- LookupError ————此基类用于派生当映射或序列所使用的键或索引无效时引发的异常
      |    +-- IndexError ————当序列抽取超出范围时将被引发。
      |    +-- KeyError ————当在现有键集合中找不到指定的映射(字典)键时将被引发。
      +-- MemoryError ————当一个操作耗尽内存但情况仍可(通过删除一些对象)进行挽救时将被引发。
      +-- NameError ————当某个局部或全局名称未找到时将被引发。
      |    +-- UnboundLocalError ————当在函数或方法中对某个局部变量进行引用,但该变量并未绑定任何值时将被引发。
      +-- OSError
      |    +-- BlockingIOError ————当一个操作会被某个设置为非阻塞操作的对象(例如套接字)所阻塞时将被引发。
      |    +-- ChildProcessError ————当一个子进程上的操作失败时将被引发。
      |    +-- ConnectionError ————与连接相关问题的基类。
      |    |    +-- BrokenPipeError ————当试图写入另一端已被关闭的管道,或是试图写入已关闭写入的套接字时将被引发。
      |    |    +-- ConnectionAbortedError ————当连接尝试被对端中止时将被引发。
      |    |    +-- ConnectionRefusedError ————当连接尝试被对端拒绝时将被引发。
      |    |    +-- ConnectionResetError ————当连接被对端重置时将被引发。
      |    +-- FileExistsError ————当试图创建一个已存在的文件或目录时将被引发。
      |    +-- FileNotFoundError ————当所请求的文件或目录不存在时将被引发。
      |    +-- InterruptedError ————当系统调用被输入信号中断时将被引发。
      |    +-- IsADirectoryError ————当请求对一个目录执行文件操作 (例如 os.remove()) 将被引发。
      |    +-- NotADirectoryError ————当请求对一个非目录对象执行目录操作 (例如 os.listdir()) 时将被引发。
      |    +-- PermissionError ————当在没有足够操作权限的情况下试图执行某个操作时将被引发 —— 例如缺少文件系统权限
      |    +-- ProcessLookupError ————当给定的进程不存在时将被引发。
      |    +-- TimeoutError ————当一个系统函数发生系统级超时的情况下将被引发。
      +-- ReferenceError ————此异常将在使用 weakref.proxy() 函数所创建的弱引用来访问该引用的某个已被作为垃圾回收的属性时被引发。
      +-- RuntimeError ————当检测到一个不归属于任何其他类别的错误时将被引发。
      |    +-- NotImplementedError
      |    +-- RecursionError ————它会在解释器检测发现超过最大递归深度时被引发。
      +-- SyntaxError ————当解析器遇到语法错误时将被引发。
      |    +-- IndentationError ————与不正确的缩进相关的语法错误的基类。 这是 SyntaxError 的一个子类。
      |         +-- TabError ————当缩进包含对制表符和空格符不一致的使用时将被引发。
      +-- SystemError ————当解释器发现内部错误,但情况看起来尚未严重到要放弃所有希望时将被引发
      +-- TypeError ————当一个操作或函数被应用于类型不适当的对象时将被引发。
      +-- ValueError ————当操作或函数接收到具有正确类型但值不适合的参数,并且情况不能用更精确的异常例如 IndexError 来描述时将被引发
      |    +-- UnicodeError ————当发生与 Unicode 相关的编码或解码错误时将被引发。
      |         +-- UnicodeDecodeError ————当在解码过程中发生与 Unicode 相关的错误时将被引发。
      |         +-- UnicodeEncodeError ————当在编码过程中发生与 Unicode 相关的错误时将被引发。
      |         +-- UnicodeTranslateError ————在转写过程中发生与 Unicode 相关的错误时将被引发。
      +-- Warning ————警告类别的基类。
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- ResourceWarning

自定义异常

除了python以及各模块的异常之外,我们也可以自己定义自己的异常类型,如下

class MyError(Exception):
    def __init__(self, msg):
        self.msg = msg

    def __str__(self):
        return self.msg

try:
    raise MyError('类型错误')
except MyError as e:
    print('My exception occurred', e)

异常的函数调用栈 traceback

  • 基本异常处理虽然可以帮助我们解决大多数问题,但是随着代码的复杂性提高,我们会发现有很多情况下简单的异常处理已经无法解决问题了,单纯的打印异常所能提供的信息会非常有限,例如:
def func1():
    print(1/0)


def func2():
    func1()


def main():
    try:
        func2()
    except Exception as e:
        print(e)


if __name__ == '__main__':
    main()
  • 该代码执行后抛出错误division by zero,但是如果要定位则是不够清晰的。此时traceback则排上了用场。
    python能“记住”引发的异常以及程序的当前状态。Python还维护着traceback(跟踪)对象,其中含有异常发生时与函数调用堆栈有关的信息。记住,异常可能在一系列嵌套较深的函数调用中引发。程序调用每个函数时,Python会在“函数调用堆栈”的起始处插入函数名。一旦异常被引发,Python会搜索一个相应的异常处理程序。如果当前函数中没有异常处理程序,当前函数会终止执行,Python会搜索当前函数的调用函数,并以此类推,直到发现匹配的异常处理程序,或者Python抵达主程序为止。这一查找合适的异常处理程序的过程就称为“堆栈辗转开解”(StackUnwinding)。解释器一方面维护着与放置堆栈中的函数有关的信息,另一方面也维护着与已从堆栈中“辗转开解”的函数有关的信息。
  • 对上述代码修改下
import traceback

def func1():
    print(1/0)


def func2():
    func1()


def main():
    try:
        func2()
    except Exception as e:
        print(e)
        traceback.print_exc()


if __name__ == '__main__':
    main()
  • 该代码执行后就可以轻松的看出代码如何调用,又是哪里的代码执行出错了
posted @ 2022-04-09 13:22  挑灯  阅读(931)  评论(0)    收藏  举报