跳至内容

异常处理

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:(无异常类型),它会捕获包括 KeyboardInterruptSystemExit 在内的所有异常,导致程序无法正常退出。

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 引入了 ExceptionGroupexcept* 语法,专为并发场景(如 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
SystemExitsys.exit() 被调用

异常继承体系:

BaseException
├── SystemExit
├── KeyboardInterrupt
├── GeneratorExit
└── Exception
    ├── ArithmeticError
    │   └── ZeroDivisionError
    ├── LookupError
    │   ├── KeyError
    │   └── IndexError
    ├── OSError
    │   ├── FileNotFoundError
    │   └── PermissionError
    ├── ValueError
    ├── TypeError
    └── RuntimeError
        └── RecursionError
最后更新于