1、异常处理基础:什么是异常、异常处理的重要性、Python中的异常类层次结构
1.1 到底什么是异常?
异常,说白了就是程序运行时出了岔子。
你写了一段代码,逻辑上看着没问题。但一跑起来,文件找不到、网络断了、内存不够了……这些都属于异常。我习惯把异常理解成「程序在正常流程之外遇到的意外状况」。
举个例子:
# 一个简单的除法
result = 10 / 0
print(result)
这段代码一执行,Python 就会抛出一个 ZeroDivisionError。程序直接崩溃,后面的代码全废了。
为什么会这样?因为分母不能为零。这是数学规则,也是程序必须遵守的底线。
嗯,这里要注意:异常不是 bug,但处理不好就会变成 bug。bug 是你代码写错了,异常是运行时环境给你使绊子。两者有本质区别。
1.2 异常处理的重要性
你想想看,一个线上跑着的自动化脚本,突然因为某个小异常挂了。后果是什么?
- 任务中断,数据丢失
- 需要人工介入,半夜爬起来重启
- 严重时可能导致整个系统雪崩
我在项目中遇到过一件事。有一次我们做日志采集脚本,跑得好好的。结果某天某个日志文件被运维误删了,脚本直接崩溃。那一整天的日志全丢了。后来复盘时发现,就少了一个 try-except 包裹。
所以异常处理的重要性,我总结为三点:
| 重要性 | 说明 |
|---|---|
| 保证程序不崩溃 | 遇到异常时优雅降级,而不是直接挂掉 |
| 提供有意义的错误信息 | 让运维人员一眼看出问题出在哪 |
| 实现自动化恢复 | 某些异常可以自动重试或回滚,无需人工干预 |
核心观点: 没有异常处理的脚本,就像没有安全带的赛车。跑得快,但出事就是大事。
1.3 Python 中的异常类层次结构
Python 的异常体系,其实是一棵继承树。我刚开始学的时候也觉得这东西有点绕,但后来发现,理解了这棵树,写异常处理就顺手多了。
最顶层的基类是 BaseException。它下面有两个主要分支:
SystemExit、KeyboardInterrupt、GeneratorExit—— 这些是系统级异常,一般不建议捕获Exception—— 这才是我们日常打交道的大头
Exception 下面又分了很多子类:
BaseException
├── SystemExit
├── KeyboardInterrupt
├── GeneratorExit
└── Exception
├── ArithmeticError
│ ├── ZeroDivisionError
│ └── FloatingPointError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── OSError
│ ├── FileNotFoundError
│ └── PermissionError
├── ValueError
├── TypeError
└── ... 还有很多
这张表我建议你记一下,写代码时经常用到:
| 异常类型 | 触发场景 | 常见例子 |
|---|---|---|
ZeroDivisionError |
除数为零 | 1/0 |
FileNotFoundError |
文件不存在 | open('不存在的文件.txt') |
ValueError |
值不合法 | int('abc') |
TypeError |
类型不匹配 | '1' + 2 |
KeyError |
字典键不存在 | {'a':1}['b'] |
IndexError |
列表索引越界 | [1,2,3][10] |
我的习惯: 捕获异常时尽量用具体的子类,而不是笼统地捕获 Exception。比如你知道可能发生 FileNotFoundError,就只捕获它。这样不会把其他意外异常也吞掉。
1.4 一个简单的异常处理示例
光说不练假把式。我们来看一个实际场景:读取配置文件。
def load_config(file_path):
try:
with open(file_path, 'r') as f:
config = f.read()
return config
except FileNotFoundError:
print(f'配置文件 {file_path} 不存在,使用默认配置')
return 'default_config'
except PermissionError:
print(f'没有权限读取 {file_path},请检查文件权限')
return None
except Exception as e:
print(f'读取配置时发生未知错误: {e}')
return None
这段代码做了三件事:
- 正常读取文件
- 文件不存在时,降级使用默认配置
- 权限不足时,给出明确提示
我曾经踩过的坑: 早期我写异常处理时,喜欢用 except Exception as e: pass。觉得这样程序就不会崩了。结果呢?异常被静默吞掉,问题根本发现不了。后来线上出了故障,查了半天才发现是某个异常被 pass 了。从那以后,我再也不敢随便 pass 异常了。
1.5 异常处理的黄金法则
总结几条我这些年积累下来的经验:
- 能精确就精确:捕获具体的异常类型,别一把抓
- 别吞异常:要么处理,要么往上抛,别静默忽略
- 日志要打全:捕获异常时一定要记录堆栈信息,方便排查
- 考虑降级方案:异常发生时,程序能不能继续跑?能的话怎么跑?
说白了,异常处理不是为了消灭异常,而是为了在异常发生时,程序还能体面地活着。
好了,这一章就到这里。下一章我们聊聊如何用 try-except-else-finally 构建更健壮的异常处理结构。