Python 异常机制深度剖析

异常是程序运行过程中发生的错误或意外情况。Python 通过一套完整的异常处理机制,让开发者能够优雅地捕获、处理这些错误,避免程序直接崩溃。

1. 异常处理的基本语法:try / except / else / finally

python 复制代码
try:
    # 可能抛出异常的代码
    x = int(input("请输入数字: "))
    result = 10 / x
except ValueError:
    print("输入不是有效数字")
except ZeroDivisionError:
    print("除数不能为零")
else:
    # 仅当 try 块未发生任何异常时执行
    print(f"计算成功,结果是 {result}")
finally:
    # 无论是否发生异常,都会执行(清理资源等)
    print("执行结束")

执行流程

  • 先执行 try 块;
  • 如果发生异常,匹配对应的 except 分支;
  • 若无异常,执行 else 块;
  • 最后无论有无异常都执行 finally 块。

2. 异常类的继承层次结构

Python 所有异常都继承自 BaseException 基类。继承树(简化版):

复制代码
BaseException
 ├── SystemExit                 # sys.exit() 触发
 ├── KeyboardInterrupt          # Ctrl+C 触发
 ├── GeneratorExit              # 生成器 close() 触发
 └── Exception                  # 所有常规异常的基类
      ├── StopIteration         # 迭代器结束
      ├── ArithmeticError
      │    ├── ZeroDivisionError
      │    ├── OverflowError
      │    └── FloatingPointError
      ├── LookupError
      │    ├── IndexError
      │    └── KeyError
      ├── TypeError
      ├── ValueError
      ├── AttributeError
      ├── ImportError
      ├── OSError
      │    └── FileNotFoundError
      ├── RuntimeError
      └── ... (数十种其他异常)

重要原则

  • 通常自定义异常应该继承自 Exception,而不是 BaseException
  • 捕获 Exception 可以捕获除 SystemExitKeyboardInterruptGeneratorExit 之外的所有异常。
  • 捕获 BaseException 会连系统退出信号一起捕获,极少这么做。
python 复制代码
try:
    # 业务代码
    ...
except Exception as e:
    # 捕获几乎所有的常规错误
    print(f"出错: {e}")

3. 手动抛出异常:raise

raise 语句允许主动触发一个异常,可以传递异常类或异常实例。

python 复制代码
def set_age(age):
    if not isinstance(age, int):
        raise TypeError("年龄必须是整数")
    if age < 0 or age > 150:
        raise ValueError("年龄超出合理范围")
    print(f"年龄设置为 {age}")

# 调用示例
try:
    set_age(-5)
except ValueError as e:
    print(e)  # 年龄超出合理范围

常见用法

  • raise ExceptionClass("描述信息")
  • raise instance:例如 raise ValueError("错误内容")
  • 不带参数重新抛出当前异常(在 except 块中)
python 复制代码
try:
    risky_operation()
except ValueError as e:
    print("记录日志...")
    raise   # 重新抛出,保留原始调用栈

4. 自定义异常

继承 Exception 或其子类,可以添加额外属性和方法。

python 复制代码
class InsufficientFundsError(Exception):
    """账户余额不足时抛出的异常"""
    def __init__(self, balance, required):
        self.balance = balance
        self.required = required
        super().__init__(f"余额不足: 需要 {required}, 现有 {balance}")

class BankAccount:
    def __init__(self, balance=0):
        self.balance = balance
    
    def withdraw(self, amount):
        if amount > self.balance:
            raise InsufficientFundsError(self.balance, amount)
        self.balance -= amount
        return self.balance

# 使用
try:
    account = BankAccount(100)
    account.withdraw(200)
except InsufficientFundsError as e:
    print(e)                # 余额不足: 需要 200, 现有 100
    print(f"余额: {e.balance}, 需要: {e.required}")

最佳实践

  • 自定义异常命名以 Error 结尾。
  • 提供有意义的错误信息和额外属性。
  • 通常不重写 __str__,利用基类的机制即可。

5. 异常传递(传播机制)

当函数内部发生异常且没有被捕获时,该异常会向调用栈的上层传递,直到被某个 try/except 捕获;如果传递到主程序仍未捕获,程序终止并打印回溯信息。

python 复制代码
def func_a():
    return 1 / 0   # 抛出 ZeroDivisionError

def func_b():
    return func_a()   # 未捕获,继续向上传递

def func_c():
    try:
        func_b()
    except ZeroDivisionError as e:
        print(f"在 func_c 中捕获: {e}")

func_c()  # 输出: 在 func_c 中捕获: division by zero

调用栈示意图

复制代码
main() -> func_c() -> func_b() -> func_a() -> ZeroDivisionError
            ↑ 捕获

重新抛出(传递)

有时需要部分处理异常(如记录日志),然后让上层继续处理。

python 复制代码
def process_file(filename):
    try:
        f = open(filename)
        # 处理文件
    except FileNotFoundError as e:
        print(f"日志: 文件 {filename} 不存在")
        raise   # 重新抛出,让上层决定是否恢复或退出

6. 异常链与 from 关键字

在处理一个异常时抛出另一个异常,可以使用 from 保留原始异常信息。

python 复制代码
try:
    value = int("abc")
except ValueError as original:
    raise RuntimeError("转换失败") from original

# 输出会显示两个异常,明确因果关系

7. assert 断言与异常

assert condition, message 在条件为假时抛出 AssertionError。通常用于调试和内部检查,不应作为业务逻辑的异常控制。

python 复制代码
def divide(a, b):
    assert b != 0, "除数不能为零"   # 开发阶段快速发现错误
    return a / b

8. 常见陷阱与注意事项

  • 捕获过于宽泛except: 会捕获 BaseException,包括 KeyboardInterruptSystemExit,导致无法正常退出程序。推荐 except Exception as e:

  • finally 中的 return 会覆盖之前的异常

    python 复制代码
    def test():
        try:
            raise ValueError
        finally:
            return 42   # 异常被吞没,函数返回 42
  • else 块与 returnelse 中的异常不会被前面的 except 捕获,因为 else 不属于 try 保护区域。

  • 性能 :异常处理比正常流程稍慢,不应将其用于常规流程控制(如用 try/except 替代 if 检查)。

9. 完整示例:多层异常处理

python 复制代码
class ValidationError(Exception):
    pass

def validate_email(email):
    if "@" not in email:
        raise ValidationError(f"无效邮箱: {email}")

def register_user(email, age):
    try:
        validate_email(email)
        if age < 18:
            raise ValueError("未成年不能注册")
    except ValidationError as e:
        print(f"邮箱错误: {e}")
        raise   # 向上传递
    except ValueError as e:
        print(f"年龄错误: {e}")
        raise

try:
    register_user("user#example.com", 20)
except ValidationError:
    print("注册失败: 请检查邮箱格式")
except ValueError:
    print("注册失败: 年龄不符合要求")

总结

概念 关键点
异常机制 try/except/else/finally 捕获与处理
继承层次 BaseException <- Exception <- 具体异常;自定义应继承 Exception
手动抛出 raise Exception("msg")
自定义异常 继承 Exception,添加有用属性
异常传递 未捕获的异常沿调用栈向上传播,直至被捕获或终止程序
异常链 from 关键字保留上下文
最佳实践 捕获具体异常、避免吞没异常、finally 用于资源释放、不滥用异常做流程控制

掌握这些内容,你就能在 Python 中设计出健壮、可维护的错误处理逻辑。

相关推荐
whitelbwwww4 小时前
C++基础--类型、函数、作用域、指针、引用、文件
开发语言·c++
leaves falling4 小时前
C/C++ const:修饰变量和指针的区别(和引用底层关系)
c语言·开发语言·c++
Ulyanov4 小时前
打造现代化雷达电子对抗仿真界面 第一篇:tkinter/ttk 现代化高级技巧与复杂布局系统设计
python·信息可视化·系统仿真·雷达电子对抗
比昨天多敲两行4 小时前
C++11新特性
开发语言·c++
xiaoye-duck4 小时前
【C++:C++11】核心特性实战:详解C++11列表初始化、右值引用与移动语义
开发语言·c++·c++11
wgzrmlrm744 小时前
SQL实现按用户偏好进行分组汇总_自定义聚合规则
jvm·数据库·python
希望永不加班4 小时前
SpringBoot 事件机制:ApplicationEvent 与监听器
java·开发语言·spring boot·后端·spring
7年前端辞职转AI4 小时前
Python 变量
python·编程语言
7年前端辞职转AI4 小时前
Python 数据类型
python·编程语言