程序运行时不可避免会遇到错误。Python 处理错误的机制就是异常——当出错时,解释器“抛出”一个异常对象,如果没人“捕获”,程序就会终止并打印 Traceback。掌握异常体系的构成,是写出健壮、好调试的 Python 代码的基础。
异常类的继承关系
Python 中所有异常都继承自 BaseException,但我们实际关心的异常绝大部分是 Exception 的子类。这个层级关系直接决定了 except 的捕获范围。
重要的继承链大致如下:
BaseException
├── SystemExit # sys.exit() 引发
├── KeyboardInterrupt # 用户按 Ctrl+C
├── GeneratorExit # 生成器关闭时
└── Exception # 所有常规错误的基类
├── ArithmeticError # 数值计算错误
│ ├── ZeroDivisionError # 除以零
│ └── OverflowError # 数值溢出
├── AssertionError # assert 语句失败
├── AttributeError # 属性访问或赋值失败
├── ImportError # 导入模块失败
│ └── ModuleNotFoundError # 找不到模块
├── LookupError # 索引或键查找失败
│ ├── IndexError # 序列索引越界
│ └── KeyError # 字典键不存在
├── NameError # 变量名未找到
│ └── UnboundLocalError # 使用了未绑定的局部变量
├── OSError # 系统调用失败
│ ├── FileNotFoundError # 文件不存在
│ ├── PermissionError # 无权限操作
│ ├── TimeoutError # 操作超时
│ └── IsADirectoryError # 用文件操作打开目录
├── RuntimeError # 其他运行时错误
├── SyntaxError # 语法错误
│ └── IndentationError # 缩进错误
├── TypeError # 类型不匹配的操作
├── ValueError # 类型正确但值不合法
└── UnicodeError # Unicode 编解码错误
关键点:
except Exception:能捕获大部分预期内的错误,但不会捕获SystemExit、KeyboardInterrupt等系统级异常,这是一个好的默认习惯。- 子类异常会被父类
except捕获,所以写多个except时要把子类放前面,否则父类会先“吃掉”异常,导致子类捕获分支永不生效。
常见内置异常速查表
| 异常类 | 典型触发场景 | 简单示例 |
|--------|-------------|----------|
| AttributeError | 访问对象不存在的属性 | [1,2,3].non_exist |
| ImportError / ModuleNotFoundError | 导入不存在的模块或包 | import nonexistent_module |
| IndexError | 列表/字符串索引越界 | lst = [1,2]; lst[5] |
| KeyError | 字典键不存在 | d = {}; d['missing'] |
| NameError | 使用未定义的变量名 | print(undefined_var) |
| TypeError | 对不兼容类型做操作 | '3' + 5 |
| ValueError | 类型正确但值不合理 | int('abc') |
| ZeroDivisionError | 除数为零 | 1 / 0 |
| FileNotFoundError | 打开不存在的文件 | open('nofile.txt') |
| PermissionError | 无权限操作文件 | open('/etc/shadow', 'w') |
| OSError | 系统级 IO 错误(父类,也直接出现) | 磁盘满、文件系统故障 |
如何诊断和利用异常
- 查看错误信息:Traceback 最后一行会给出异常类型和描述,如
ValueError: invalid literal for int() with base 10: 'abc',它已经告诉你哪里出了问题。 - 用
try/except精准捕获:避免写except:或except Exception:一把抓。先捕获最具体的异常,最后再用宽泛的兜底。 - 自定义异常:继承
Exception或其子类,简单就能创建业务含义明确的异常,提高代码可读性。
class OrderValidationError(ValueError):
"""订单数据校验失败"""
pass
def validate_order(data):
if data['amount'] <= 0:
raise OrderValidationError("订单金额必须大于0")
在实际开发中,熟练掌握异常体系有几个直接好处:
- 排查问题更快:一看异常类型就大概知道是哪种错误。
- 代码更健壮:懂得哪些异常可能发生,才能写出合理的捕获和处理逻辑。
- 团队协作更顺畅:统一的自定义异常让错误传递有统一的“语言”,而不是靠返回码或打印日志四处散落。