2025-08-21 Python进阶4——错误和异常
文章目录
- 1 错误的两种类型
- 1.1 语法错误(解析错误)
- 1.2 异常
- 2 异常的处理
- 2.1 基本语法
- 2.2 处理多种异常
- 2.3 异常的继承关系
- 2.4 获取异常信息
- 2.5 else 子句
- 2.6 异常在函数中的传播
- 3 主动触发异常
- 3.1 基本用法
- 3.2 重新引发异常
- 4 异常链
- 4.1 隐式异常链
- 4.2 显式异常链
- 4.3 禁用异常链
- 5 用户自定义异常
- 5.1 定义自定义异常
- 5.2 使用自定义异常
- 6 清理操作
- 6.1 finally 子句
- 6.2 with 语句
- 7 处理多个不相关的异常
- 7.1 引发异常组
- 7.2 捕获异常组
- 8 为异常添加注释
- 9 总结
1 错误的两种类型
Python 中的错误主要分为两类:语法错误和异常。
1.1 语法错误(解析错误)
语法错误是最常见的错误类型,通常是由于代码不符合 Python 的语法规则导致的。当解析器检测到语法错误时,会指出错误位置并终止程序。
示例:
# 缺少冒号
while True print('Hello world')
错误信息:
File "<stdin>", line 1while True print('Hello world')^^^^^
SyntaxError: invalid syntax
说明:错误信息中的箭头指向解析器检测到错误的位置,但实际需要修复的可能是其附近的语法(如上例中 while True
后缺少冒号)。
1.2 异常
即使代码语法正确,在执行过程中也可能发生错误,这类错误称为异常。异常不会导致解析器崩溃,但如果不处理,会导致程序终止并显示错误信息。
常见异常示例:
# 除以零
10 * (1/0) # ZeroDivisionError: division by zero# 使用未定义的变量
4 + spam*3 # NameError: name 'spam' is not defined# 类型不匹配
'2' + 2 # TypeError: can only concatenate str (not "int") to str
异常信息解读:
- 错误信息最后一行显示异常类型(如
ZeroDivisionError
)和详细描述 - 前面的内容是堆栈回溯,显示异常发生的位置和调用链
2 异常的处理
Python 提供 try...except
语句来捕获和处理异常,使程序在遇到异常时能够继续执行。
2.1 基本语法
try:# 可能引发异常的代码risky_operation()
except ExceptionType:# 处理特定类型异常的代码handle_exception()
示例:处理用户输入非数字的情况
while True:try:x = int(input("请输入一个数字: "))breakexcept ValueError:print("无效的数字,请重新输入...")
工作原理:
- 执行
try
子句中的代码 - 如果没有异常,跳过
except
子句,继续执行后续代码 - 如果发生异常,检查异常类型是否与
except
后指定的类型匹配 - 若匹配,执行
except
子句中的处理代码,然后继续执行后续代码 - 若不匹配,异常会传递到外层代码,若没有其他处理器,则程序终止
2.2 处理多种异常
可以使用多个 except
子句处理不同类型的异常,也可以在一个 except
中处理多种异常。
try:# 可能引发多种异常的代码f = open('myfile.txt')s = f.readline()i = int(s.strip())
except OSError as err:print(f"操作系统错误: {err}")
except ValueError:print("无法将数据转换为整数")
except Exception as err:print(f"意外错误: {err=}, {type(err)=}")raise # 重新引发异常,让上层处理
捕获多种异常的方式:
# 方式1:多个 except 子句
except RuntimeError:handle_runtime_error()
except TypeError:handle_type_error()# 方式2:元组指定多个异常
except (RuntimeError, TypeError, NameError):handle_these_errors()
2.3 异常的继承关系
异常处理遵循继承关系:如果一个 except
子句指定了基类,则它会捕获该基类及其派生类的所有异常。
class B(Exception):passclass C(B):passclass D(C):passfor cls in [B, C, D]:try:raise cls()except D:print("D")except C:print("C")except B:print("B")
# 输出: B C D
注意:如果将 except B
放在最前面,将会捕获所有 B
、C
、D
类型的异常,因为它们都是 B
的派生类。
2.4 获取异常信息
可以通过 as
关键字将异常实例绑定到变量,从而获取详细信息。
try:raise Exception('spam', 'eggs')
except Exception as inst:print(type(inst)) # 异常类型: <class 'Exception'>print(inst.args) # 异常参数: ('spam', 'eggs')print(inst) # 异常字符串表示: ('spam', 'eggs')x, y = inst.args # 解包参数print(f'x = {x}, y = {y}')
2.5 else 子句
try...except
可以包含 else
子句,用于执行那些在 try
子句没有引发异常时才需要执行的代码。
import sysfor arg in sys.argv[1:]:try:f = open(arg, 'r')except OSError:print(f'无法打开文件: {arg}')else:# 只有文件成功打开时才执行print(f'{arg} 有 {len(f.readlines())} 行')f.close()
优势:将不需要异常保护的代码放在 else
中,避免意外捕获其他代码引发的异常。
2.6 异常在函数中的传播
异常处理程序不仅处理 try
子句中直接发生的异常,还处理 try
子句中调用的函数(包括间接调用)中发生的异常。
def this_fails():x = 1/0try:this_fails()
except ZeroDivisionError as err:print(f'处理运行时错误: {err}')
# 输出: 处理运行时错误: division by zero
3 主动触发异常
使用 raise
语句可以主动触发指定的异常。
3.1 基本用法
# 触发异常
raise NameError('这是一个自定义错误信息')# 触发异常类(会隐式创建实例)
raise ValueError # 等价于 raise ValueError()
3.2 重新引发异常
在异常处理后,可以使用不带参数的 raise
重新引发当前异常,让上层代码继续处理。
try:raise NameError('HiThere')
except NameError:print('一个异常被捕获!')raise # 重新引发异常
输出:
一个异常被捕获!
Traceback (most recent call last):File "<stdin>", line 2, in <module>
NameError: HiThere
4 异常链
当一个异常导致另一个异常时,可以使用异常链来表示这种关系,使错误信息更清晰。
4.1 隐式异常链
当在 except
子句中引发新异常时,Python 会自动创建异常链。
try:open("database.sqlite")
except OSError:raise RuntimeError("无法处理错误")
错误信息:
Traceback (most recent call last):File "<stdin>", line 2, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'database.sqlite'During handling of the above exception, another exception occurred:Traceback (most recent call last):File "<stdin>", line 4, in <module>
RuntimeError: 无法处理错误
4.2 显式异常链
使用 from
子句可以显式指定异常之间的因果关系。
def func():raise ConnectionErrortry:func()
except ConnectionError as exc:# 显式指定异常原因raise RuntimeError('无法打开数据库') from exc
错误信息:
The above exception was the direct cause of the following exception:Traceback (most recent call last):File "<stdin>", line 4, in <module>
RuntimeError: 无法打开数据库
4.3 禁用异常链
使用 from None
可以禁用自动异常链。
try:open('database.sqlite')
except OSError:raise RuntimeError from None
5 用户自定义异常
可以通过创建新的异常类来定义自己的异常类型,通常继承自 Exception
类。
5.1 定义自定义异常
class CustomError(Exception):"""自定义异常类"""passclass ValueTooSmallError(CustomError):"""值太小的异常"""def __init__(self, value, min_value):self.value = valueself.min_value = min_valueself.message = f"值 {value} 太小,最小值为 {min_value}"super().__init__(self.message)
5.2 使用自定义异常
def check_value(n):if n < 10:raise ValueTooSmallError(n, 10)return Truetry:check_value(5)
except ValueTooSmallError as e:print(f"捕获到自定义异常: {e}")
最佳实践:
- 异常类名通常以 “Error” 结尾
- 保持异常类简单,主要用于传递错误信息
- 适当组织异常的继承层次,便于分类处理
6 清理操作
在有些情况下,无论是否发生异常,都需要执行一些清理操作(如释放资源)。Python 提供了 finally
子句和 with
语句来处理这种情况。
6.1 finally 子句
finally
子句中的代码无论是否发生异常都会执行。
try:raise KeyboardInterrupt
finally:print('程序即将退出!')
输出:
程序即将退出!
Traceback (most recent call last):File "<stdin>", line 2, in <module>
KeyboardInterrupt
复杂场景中的行为:
- 如果
try
子句发生异常且未被处理,finally
执行后异常会被重新引发 except
或else
子句中发生的异常,会在finally
执行后重新引发- 如果
finally
包含break
、continue
或return
,异常不会被重新引发 finally
中的return
会覆盖try
或except
中的return
示例:
def divide(x, y):try:result = x / yexcept ZeroDivisionError:print("除以零!")else:print("结果是", result)finally:print("执行清理操作")divide(2, 1)
# 输出:
# 结果是 2.0
# 执行清理操作divide(2, 0)
# 输出:
# 除以零!
# 执行清理操作
6.2 with 语句
with
语句用于定义一个资源的使用范围,确保资源在使用后被正确清理(如文件自动关闭)。
# 传统方式
f = open("myfile.txt")
try:for line in f:print(line, end="")
finally:f.close()# 使用 with 语句(更简洁)
with open("myfile.txt") as f:for line in f:print(line, end="")
工作原理:支持 with
语句的对象实现了上下文管理协议(__enter__
和 __exit__
方法),__exit__
方法会负责资源清理。
7 处理多个不相关的异常
Python 3.11 引入了 ExceptionGroup
,用于同时处理多个不相关的异常,这在并发场景中特别有用。
7.1 引发异常组
def f():excs = [OSError('错误 1'), SystemError('错误 2')]raise ExceptionGroup('发生多个问题', excs)f()
输出:
+ Exception Group Traceback (most recent call last):| File "<stdin>", line 1, in <module>| f()| File "<stdin>", line 3, in f| raise ExceptionGroup('发生多个问题', excs)| ExceptionGroup: 发生多个问题 (2 sub-exceptions)+-+---------------- 1 ----------------| OSError: 错误 1+---------------- 2 ----------------| SystemError: 错误 2+------------------------------------
7.2 捕获异常组
使用 except*
可以选择性地处理异常组中特定类型的异常。
def f():raise ExceptionGroup("group1",[OSError(1),SystemError(2),ExceptionGroup("group2",[OSError(3), RecursionError(4)])])try:f()
except* OSError as e:print("处理所有 OSError 异常")
except* SystemError as e:print("处理所有 SystemError 异常")
输出:
处理所有 OSError 异常
处理所有 SystemError 异常+ Exception Group Traceback (most recent call last):| ...| ExceptionGroup: group1 (1 sub-exception)+-+---------------- 1 ----------------| ExceptionGroup: group2 (1 sub-exception)+-+---------------- 1 ----------------| RecursionError: 4+------------------------------------
8 为异常添加注释
使用 add_note()
方法可以为异常添加额外的上下文信息,帮助调试。
try:raise TypeError('类型错误')
except Exception as e:e.add_note('补充信息 1')e.add_note('补充信息 2')raise
输出:
Traceback (most recent call last):File "<stdin>", line 2, in <module>
TypeError: 类型错误
补充信息 1
补充信息 2
应用场景:为异常添加发生时的上下文(如迭代次数、处理的数据等),使错误信息更完整。
9 总结
- Python 错误分为语法错误和异常两类
- 使用
try...except
捕获和处理异常,可指定处理特定类型的异常 raise
语句用于主动触发异常- 异常链用于表示异常之间的因果关系
- 可以定义自定义异常类来表示特定错误
finally
子句和with
语句用于确保清理操作的执行ExceptionGroup
用于处理多个不相关的异常add_note()
可给异常添加额外信息
掌握异常处理机制能够编写更健壮、更易维护的程序,特别是在处理用户输入、文件操作、网络请求等可能出现意外情况的场景中尤为重要。