Python系列(9)- Python 异常处理机制


1. 错误和异常

    编程开发时一般会遇到 2 种类型的错误,分别为语法错误和运行时错误。

    语法错误 (Syntax Error): Python 解释器在解析代码时遇到的错误,比如拼写错误、不符合语法规则等。Python 解释器会提示错误的类型和出错的位置,便于开发者及时纠正错误,在错误没有得到纠正之前,代码无法被正常执行。

    运行时错误(Runtime Error): 程序代码在语法上都是正确的,但在运行时发生了错误。在 Python 中,把这种运行时产生错误的情况叫做异常 (Exception)。

    Python 常见的 Exception 子类异常:

异常类型 描述
AssertionError 当 assert 关键字后的条件为假时,程序运行会停止并抛出 AssertionError 异常
AttributeError 当试图访问的对象属性不存在时抛出的异常
IndexError 索引超出序列范围会引发此异常
KeyError 字典中查找一个不存在的关键字时引发此异常
MemoryError 内存溢出错误
NameError 尝试访问一个未声明的变量时,引发此异常
OSError 操作系统错误
RuntimeError 一般的运行时错误
SyntaxError Python 语法错误
StopInteration 迭代器没有更多值
TypeError 不同类型数据之间的无效操作
ValueError 传入无效的参数
ZeroDivisionError 除法运算中除数为 0 引发此异常

       

2. 异常处理

    1) try ... except 语句

        Python 异常处理机制提供了 try ... except 语句捕获并处理异常,语法格式如下:

            try:
                代码块
            except [(Error1, Error2, ... ) [as e]]:
                异常处理代码块
            except [Exception [as e]]:
                默认异常处理

        解释说明:
            
            (1) try: try 语句到第一个 except 之间称为 try 块,用来监控执行时可能会发生异常的代码;
            (2) except [(Error1, Error2, ... ) [as e]]: 一个 except 块可以捕获多种异常类型(Error1, Error2, ...),[as e] 给异常类型取个别名 e;
            (3) except [Exception [as e]]: except 语句捕获异常信息,无法判断异常类型,调用默认异常处理;

        示例:

            #!/usr/bin/python3
            # -*- coding: UTF-8 -*-

            try:
                a = 'text'
                b = 0
                c = a / b
                print("c:", c)
            except (NameError, ZeroDivisionError) as e:
                print(repr(e))
            except Exception as e:
print(e.args)
print(repr(e))

        输出结果如下:

            TypeError("unsupported operand type(s) for /: 'str' and 'int'")

        注:a 是 str 类型,a 不能进行算术运算。如果把 a = 'text' 这行去掉或注释掉,输出结果如下:

                NameError("name 'a' is not defined")

            如果修改 a = 1,输出结果如下:

                ZeroDivisionError('division by zero')

            repr(e) 函数可以返回异常对象的类型信息,e.args 可以返回异常的错误编号和描述字符串。

    2) try ... except else 语句

        Python 异常处理机制还提供了一个 else 语句,在 try except 语句的基础上添加一个 else 语句,语法格式如下:

            try:
                代码块
            except [(Error1, Error2, ... ) [as e]]:
                异常处理代码块
            except [Exception [as e]]:
                默认异常处理
            else:
                没有异常处理

        当 try 块没有捕获到任何异常时,else 语句才会被执行;如果 try 块捕获到异常,调用对应的 except 语句处理异常,else 语句不会被执行。

        示例:

            #!/usr/bin/python3
            # -*- coding: UTF-8 -*-

            try:
                a = 1
                b = 2
                c = a / b
                print("c:", c)
            except (NameError, ZeroDivisionError) as e:
                print(repr(e))
            except Exception as e:
                print(repr(e))
            else:
                print('No exception')    

        输出结果如下:

            c: 0.5
            No exception                

    3) try ... except finally 语句

        Python 内存回收机制,能帮我们回收变量、类对象占用的内存,而无法自动完成类似关闭文件、数据库连接等工作。

        Python 异常处理机制还提供了一个 finally 语句,用来为 try 块的代码做清理工作(比如:关闭文件句柄、数据库连接等),语法格式如下:

            try:
                代码块
            except [(Error1, Error2, ... ) [as e]]:
                异常处理代码块
            except [Exception [as e]]:
                默认异常处理
            [else:]
                [没有异常处理]
            finally:
                清理工作

            注:[] 括表示非必选项。

        无论 try 块是否发生异常,是否包含 else 语句,最终都会进入 finally 语句。

        基于 finally 语句的这种特性,在某些情况下,当 try 块中的程序打开了一些物理资源(文件、数据库连接等)时,由于这些资源必须手动回收,而回收工作通常就放在 finally 块中。

        必须使用 finally 块吗?当然不是,但使用 finally 块是比较好的选择。首先,try 块不适合做资源回收工作,因为一旦 try 块中的某行代码发生异常,则其后续的代码将不会得到执行;其次 except 和 else 也不适合,它们都可能不会得到执行。而 finally 块中的代码,无论 try 块是否发生异常,该块中的代码都会被执行。

        示例:

            #!/usr/bin/python3
            # -*- coding: UTF-8 -*-

            try:
                a = 1
                b = 2
                c = a / b
                print("c:", c)
            except (NameError, ZeroDivisionError) as e:
                print(repr(e))
            except Exception as e:
                print(repr(e))
            #else:
            #    print('No exception')                    
            finally:
                print('finally')

        输出结果如下:

            c: 0.5
            finally

    4) raise 语句

        Python 异常处理机制提供了手动抛出异常的 raise 语句,语法格式如下:

            raise [exceptionName [(reason)]]

        解释说明:

            (1) raise:raise 后不带参数,引发当前上下文中捕获的异常(也可以在 except 块中),或默认引发 RuntimeError 异常;
            (2) raise 异常类名称:raise 后带一个异常类名称,表示引发执行类型的异常;
            (3) raise 异常类名称(描述信息):在引发指定类型的异常的同时,附带异常的描述信息;

        示例:

            #!/usr/bin/python3
            # -*- coding: UTF-8 -*-

            try:
                a = 'text'
                b = 2

                if type(a) != int:
                    raise

                c = a / b
                print("c:", c)
            except Exception as e:
                print(repr(e))

        输出结果如下:

            RuntimeError('No active exception to reraise')

        注:a 是 str 类型,a 不能进行算术运算。在程序运行到 c = a / b 语句之前,我们提前判断 a 的类型,如果 a 不是整型,运行不带参数的 raise (引发一个 RuntimeError 异常)。

            如果把不带参数的 raise 改成 raise TypeError('\'a\' must be an integer'),输出结果如下:

                TypeError("'a' must be an integer")

            Exception 类是 TypeError 类的基类, e 是个基类变量,子类赋值给基类变量,会表现出多态。

 

3. 获取更详细异常信息

    有 2 种方式可以更详细异常信息,分别是:

        (1) 使用 sys 模块中的 exc_info 方法;
        (2) 使用 traceback 模块中的相关函数。

    1) sys.exc_info() 方法

        示例:

            #!/usr/bin/python3
            # -*- coding: UTF-8 -*-

            import sys

            try:
                a = 1
                b = 0
                c = a / b
                print("c:", c)
            except Exception as e:
                print(repr(e))
                print(sys.exc_info())

        输出结果如下:

            ZeroDivisionError('division by zero')
            (<class 'ZeroDivisionError'>, ZeroDivisionError('division by zero'), <traceback object at 0x000001C77C19EB00>)

    2) traceback 模块

        示例:

            #!/usr/bin/python3
            # -*- coding: UTF-8 -*-

            import traceback

            def func():
                func2()

            def func2():
                func3()

            def func3():
                raise Exception('Unknown error')

            try:
                func()
            except Exception as e:
                traceback.print_exc()
                #traceback.print_exc(file=open('log.txt', 'a'))

        输出结果如下:

            Traceback (most recent call last):
            File "d:/pythonDemo/except2.py", line 16, in <module>
                func()
            File "d:/pythonDemo/except2.py", line 7, in func
                func2()
            File "d:/pythonDemo/except2.py", line 10, in func2
                func3()
            File "d:/pythonDemo/except2.py", line 13, in func3
                raise Exception('Unknown error')
            Exception: Unknown error

        注:traceback.print_exc() 方法可以生成一个包含异常详细信息的 log.txt 文件。


4. 自定义异常类

    Python 中自定义异常类,可以通过继承内置的 Exception 类或其子类来实现。自定义异常类可以让我们更精确地描述和处理特定的错误情况。

    示例:

        #!/usr/bin/python3
        # -*- coding: UTF-8 -*-

            class CustomError(Exception):
                def __init__(self, message):
                    super().__init__(message)

                def __str__(self):
                    return super().__str__()

            try:
                a = 1
                b = 0

                if b == 0:
                    raise CustomError('division by zero')

                c = a / b
                print("c:", c)
            except Exception as e:
                print(e)
                print(repr(e))
                print(e.args)

    输出结果如下:

        division by zero
        CustomError('division by zero')
        ('division by zero',)

    自定义的 CustomError 异常类,实现了内置的异常类 ZeroDivisionError 的功能。把判断 b == 0 行和 raise 行注释掉,输出结果如下:

        division by zero
        ZeroDivisionError('division by zero')
        ('division by zero',)


5. 异常链(Exception Chaining)

    在Python中,异常链接(Exception Chaining)是指在处理一个异常时抛出另一个异常的技术。这样可以保留原始异常的信息,同时提供新的异常信息。这种机制在调试和错误跟踪时特别有用,因为它保留了异常发生的完整上下文。

    Python 通过两个属性来支持异常链接:显式链 (__cause__) 和隐式链 (__context__)。

    1) 显式链 (__cause__)

        用于显示一个异常是由另一个异常引发的。通过 raise new_exception from original_exception 语法来使用。

        示例:

            #!/usr/bin/python3
            # -*- coding: UTF-8 -*-
            
            try:

                try:
                    1 / 0
                except ZeroDivisionError as e:
                    raise ValueError('Exception Chaining error') from e
                
            except Exception as e2:
                print(repr(e2))
                print(repr(e2.__cause__))

        输出结果如下:

            ValueError('Exception Chaining error')
            ZeroDivisionError('division by zero')

    2) 隐式链 (__context__)

        用于记录在处理一个异常的过程中发生的另一个异常。这在没有使用 from 语句时自动设置。

        示例:

            #!/usr/bin/python3
            # -*- coding: UTF-8 -*-
            
            try:

                try:
                    1 / 0
                except ZeroDivisionError as e:
                    raise ValueError('Exception Chaining error')
                
            except Exception as e2:
                print(repr(e2))
                print(repr(e2.__context__))

        输出结果如下:

            ValueError('Exception Chaining error')
            ZeroDivisionError('division by zero')


posted @ 2024-08-26 12:32  垄山小站  阅读(51)  评论(0编辑  收藏  举报