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')