异常处理
Python 使用异常机制来处理运行时错误,防止程序因未预料的错误而崩溃。本篇介绍从基础捕获到 Python 3.11+ 异常组的完整用法。
异常的概念
异常分为两类:
- 语法错误(SyntaxError):代码不符合 Python 语法规则,在解析阶段即报错。
- 运行时错误(RuntimeError):代码语法正确,但执行时出现问题(如除以零、访问不存在的键)。
# 语法错误:代码根本不会执行
# if x > 0 # SyntaxError: expected ':'
# 运行时错误
x = 10 / 0 # ZeroDivisionError
d = {}
d["key"] # KeyError基本 try-except 语法
try:
result = 10 / 0
except ZeroDivisionError:
print("不能除以零")多分支捕获
try:
value = int("abc")
except ValueError:
print("无效的整数格式")
except TypeError:
print("类型错误")捕获多个异常类型
try:
data = {}
print(data["key"])
except (KeyError, IndexError) as e:
print(f"查找失败: {e}")捕获所有异常
try:
risky_operation()
except Exception as e:
# Exception 是所有非系统退出异常的基类
print(f"发生错误: {type(e).__name__}: {e}")避免裸
except:(无异常类型),它会捕获包括 KeyboardInterrupt、SystemExit 在内的所有异常,导致程序无法正常退出。else 与 finally
try:
f = open("data.txt", "r")
content = f.read()
except FileNotFoundError as e:
print(f"文件不存在: {e}")
except PermissionError as e:
print(f"权限不足: {e}")
else:
# 只有 try 块没有异常时才执行
print(f"文件内容: {content}")
finally:
# 无论是否有异常都会执行,通常用于释放资源
print("操作结束")更好的写法是使用 with 语句自动管理资源(见《with 语法与上下文管理》):
try:
with open("data.txt") as f:
content = f.read()
except FileNotFoundError:
print("文件不存在")主动抛出异常
def set_age(age: int) -> None:
if not isinstance(age, int):
raise TypeError(f"age 必须是整型,收到 {type(age).__name__}")
if age < 0 or age > 150:
raise ValueError(f"age 超出合理范围: {age}")
# 在 except 中重新抛出
try:
set_age(-1)
except ValueError as e:
print(f"捕获到: {e}")
raise # 重新抛出同一异常(保留原始 traceback)自定义异常
class AppError(Exception):
"""应用级别异常基类"""
pass
class DatabaseError(AppError):
def __init__(self, message: str, code: int = 0):
super().__init__(message)
self.code = code
def __str__(self) -> str:
return f"[DB-{self.code}] {super().__str__()}"
raise DatabaseError("连接超时", code=503)assert 断言
断言用于在开发阶段验证内部假设,不应用于用户输入验证(生产环境可通过 -O 参数禁用断言):
def divide(a: float, b: float) -> float:
assert b != 0, "除数不能为零"
return a / b
# AssertionError: 除数不能为零
divide(10, 0)异常链(Python 3)
try:
open("missing.txt")
except FileNotFoundError as e:
raise RuntimeError("初始化失败") from e
# 输出会显示两个异常及其关联关系
# 使用 from None 隐藏原始异常
try:
open("missing.txt")
except FileNotFoundError:
raise RuntimeError("配置文件缺失") from None异常组与 except*(Python 3.11+)
Python 3.11 引入了 ExceptionGroup 和 except* 语法,专为并发场景(如 asyncio 任务组)设计,允许同时抛出和处理多个异常:
# 创建异常组
def fetch_data():
raise ExceptionGroup(
"网络错误组",
[
ConnectionError("连接服务器 A 失败"),
TimeoutError("请求服务器 B 超时"),
ExceptionGroup(
"子错误组",
[OSError("磁盘 I/O 错误")]
)
]
)
# except* 会匹配异常组中所有符合类型的异常
try:
fetch_data()
except* ConnectionError as eg:
print(f"连接错误: {eg.exceptions}")
except* TimeoutError as eg:
print(f"超时错误: {eg.exceptions}")except* 与普通 except 不同:它不会在第一个匹配后停止,而是遍历整个异常组,将所有匹配类型的异常收集到 eg.exceptions 中。未被任何 except* 捕获的异常会重新抛出。获取异常详情
import sys
import traceback
try:
1 / 0
except ZeroDivisionError as e:
# 方式一:直接打印异常信息
print(type(e).__name__, e)
# 方式二:获取完整 traceback
traceback.print_exc()
# 方式三:获取 traceback 字符串
tb_str = traceback.format_exc()
# 方式四:通过 sys.exc_info() 获取行号
exc_type, exc_value, exc_tb = sys.exc_info()
print(f"文件: {exc_tb.tb_frame.f_code.co_filename}")
print(f"行号: {exc_tb.tb_lineno}")常见内置异常类型
| 异常 | 触发场景 |
|---|---|
ValueError | 参数值不合法(如 int("abc")) |
TypeError | 类型不匹配(如 1 + "a") |
KeyError | 字典键不存在 |
IndexError | 列表索引越界 |
AttributeError | 访问不存在的属性 |
ImportError | 模块导入失败 |
FileNotFoundError | 文件不存在(OSError 子类) |
PermissionError | 权限不足(OSError 子类) |
ZeroDivisionError | 除以零 |
RecursionError | 递归深度超限 |
StopIteration | 迭代器耗尽 |
RuntimeError | 运行时一般错误 |
NotImplementedError | 抽象方法未实现 |
MemoryError | 内存不足 |
KeyboardInterrupt | 用户按 Ctrl+C |
SystemExit | sys.exit() 被调用 |
异常继承体系:
BaseException
├── SystemExit
├── KeyboardInterrupt
├── GeneratorExit
└── Exception
├── ArithmeticError
│ └── ZeroDivisionError
├── LookupError
│ ├── KeyError
│ └── IndexError
├── OSError
│ ├── FileNotFoundError
│ └── PermissionError
├── ValueError
├── TypeError
└── RuntimeError
└── RecursionError最后更新于