一、开篇:程序不会总按你的想法运行
无论你写得多小心,程序总会遇到意外情况:文件不存在、网络超时、用户输入了奇怪的内容、数据库连接断开……这些不是bug,而是程序运行时的"正常意外"。Python的异常处理机制就是为这些情况设计的。
⌨️ 先感受异常:
# 常见异常一览
# 1. 语法错误——代码写错了,程序无法开始运行
# print("Hello" # SyntaxError —— 缺少右括号
# 2. 运行时异常——代码语法正确,运行时出问题
# print(1 / 0) # ZeroDivisionError —— 除以零
# int("abc") # ValueError —— 无法转换
# [1,2,3][10] # IndexError —— 索引越界
# {}["key"] # KeyError —— 键不存在
# open("no.txt") # FileNotFoundError —— 文件不存在
# 3. 逻辑错误——程序正常运行,结果不符合预期
# def avg(a, b): return a + b / 2 # 应该(a+b)/2
二、Python的异常层次结构
2.1 异常继承树
# Python所有异常都继承自BaseException
# 主要的继承层次:
# BaseException
# ├── SystemExit —— sys.exit()引发
# ├── KeyboardInterrupt —— Ctrl+C
# ├── GeneratorExit —— 生成器关闭
# └── Exception —— 所有"常规"异常的基类
# ├── ArithmeticError
# │ ├── ZeroDivisionError
# │ └── OverflowError
# ├── LookupError
# │ ├── IndexError
# │ └── KeyError
# ├── ValueError
# │ └── UnicodeError
# ├── TypeError
# ├── NameError
# ├── FileNotFoundError (OSError的子类)
# ├── ImportError
# ├── RuntimeError
# └── … 更多
# 验证继承关系
print(issubclass(ZeroDivisionError, ArithmeticError)) # True
print(issubclass(ZeroDivisionError, Exception)) # True
print(issubclass(KeyboardInterrupt, Exception)) # False! 它是BaseException的直接子类
2.2 常见异常分类
# ⌨️ 按类别理解异常——比死记硬背容易得多
# === 输入/值问题 ===
# ValueError —— 类型对,值不合理(int("abc"))
# TypeError —— 类型不匹配("2" + 2)
# KeyError —— 字典键不存在
# IndexError —— 列表索引越界
# AttributeError —— 对象没有这个属性
# === 计算问题 ===
# ZeroDivisionError —— 除以零
# OverflowError —— 数值溢出
# FloatingPointError —— 浮点运算错误(极少见)
# === I/O问题 ===
# FileNotFoundError —— 文件不存在
# PermissionError —— 权限不足
# IOError/OSError —— 其他I/O错误
# ConnectionError —— 网络连接问题
# === 代码问题 ===
# NameError —— 变量名不存在
# SyntaxError —— 语法错误(无法捕获,代码根本不会运行)
# IndentationError —— 缩进错误
# ImportError —— 模块导入失败
# RecursionError —— 递归深度超限
# === 系统问题 ===
# KeyboardInterrupt —— 用户中断(Ctrl+C)
# SystemExit —— sys.exit()
# MemoryError —— 内存不足
三、异常的对象属性
# 异常不只是"错误消息"——它上面有丰富的信息
try:
result = 1 / 0
except ZeroDivisionError as e:
# 异常的类型
print(f"类型: {type(e).__name__}")
# 异常消息
print(f"消息: {e}")
# 异常的参数
print(f"参数: {e.args}")
# 异常的追踪信息
import traceback
print(f"追踪: {traceback.format_exc()[:100]}…")
# 异常可以携带自定义数据
class ValidationError(Exception):
def __init__(self, message, field, value):
super().__init__(message)
self.field = field
self.value = value
try:
raise ValidationError("无效输入", "email", "not-email")
except ValidationError as e:
print(f"错误字段: {e.field}")
print(f"错误值: {e.value}")
print(f"错误消息: {e}")
四、异常的设计哲学
4.1 EAFP vs LBYL
# 两种处理"可能出错"的哲学
# LBYL (Look Before You Leap) —— 三思而后行
# 在做操作之前检查条件
def get_value_lbyl(data, key):
if key in data: # 先检查
return data[key]
return None
# EAFP (Easier to Ask Forgiveness than Permission) —— 先斩后奏
# 直接做,出错了再处理
def get_value_eafp(data, key):
try:
return data[key] # 直接做
except KeyError:
return None # 出错了再处理
# 💡 Python推荐EAFP风格
# 原因:
# 1. 更简洁——不需要写检查代码
# 2. 更安全——检查和操作之间,状态可能被其他线程改变(竞态条件)
# 3. 更快——大多数情况下不会出错,try块几乎没有性能损耗
# 但注意:EAFP不适用于所有场景
# 如果出错的可能性很高、或者异常很"重"——LBYL可能更好
4.2 什么时候该抛出异常
# ✅ 应该抛出异常的场景:
# 1. 前置条件不满足
def transfer_money(from_acc, to_acc, amount):
if amount <= 0:
raise ValueError("转账金额必须大于0")
if from_acc.balance < amount:
raise ValueError("余额不足")
# … 执行转账
# 2. 后置条件不满足
def create_file(filepath):
# … 创建文件 …
if not os.path.exists(filepath):
raise RuntimeError(f"文件创建失败: {filepath}")
# 3. 不支持的/未实现的操作
class BaseExporter:
def export(self):
raise NotImplementedError("子类必须实现export()")
# ❌ 不应该抛出异常的场景:
# 使用assert而不是异常(assert可以被优化掉)
# 正常的控制流(用if/else而不是try/except)
# 可预见的"非错误"情况(如文件不存在时返回None)
五、总结
理解异常的分类和设计哲学是写好Python代码的基础。记住:异常不是bug,而是程序运行时可能遇到的"正常意外"。
💡 核心要点:
✅ 把异常看作一种"通信机制":当你无法完成承诺的功能时,抛出异常来告知调用者。


