程序出错不可怕,可怕的是直接崩溃。异常处理让程序"优雅地失败":该报错时报错,该兜底时兜底。配合调试技巧,你就能从"看到 Traceback 就慌"进阶到"三分钟定位问题"。
1. 常见异常类型
| 异常 | 触发场景 | 示例 |
|---|---|---|
| ValueError | 值合法但不符合语义 | int("abc") |
| TypeError | 类型不匹配 | "a" + 1 |
| IndexError | 下标越界 | [1,2][5] |
| KeyError | 字典键不存在 | {"a":1}["b"] |
| FileNotFoundError | 文件不存在 | open("nope.txt") |
| ZeroDivisionError | 除以零 | 1 / 0 |
2. try / except 基础
把可能出错的代码放进 try,用 except 捕获。可以捕获特定异常,也可以多个 except 分别处理:
try:
num = int(input("请输入数字: "))
print(10 / num)
except ValueError:
print("输入的不是数字!")
except ZeroDivisionError:
print("不能除以零!")
捕获异常后要访问异常对象,用 as e 拿到它:
try:
data = {"name": "Ada"}.get("age") + 1
except TypeError as e:
print("出错了:", e)
3. else 与 finally
else 在 try 没出错时执行;finally 无论是否出错都执行,适合放清理代码:
try:
f = open("data.txt", encoding="utf-8")
content = f.read()
except FileNotFoundError:
print("文件不存在")
else:
print(f"读取成功,共 {len(content)} 字符")
finally:
print("清理:关闭文件")
日常读写文件直接用 with 即可自动关闭,比手写 finally 更简洁。
4. 主动抛出异常:raise
自己写函数时,遇到非法输入应主动 raise,把问题暴露在源头:
def withdraw(balance, amount):
if amount < 0:
raise ValueError("取款金额不能为负")
if amount > balance:
raise ValueError("余额不足!")
return balance - amount
try:
withdraw(100, 200)
except ValueError as e:
print("取款失败:", e)
5. 自定义异常
内置异常不够表达业务语义时,可以继承 Exception 自定义异常:
class InsufficientFundsError(Exception):
"""余额不足异常"""
pass
def pay(balance, price):
if price > balance:
raise InsufficientFundsError(
f"需要 {price},余额只有 {balance}"
)
return balance - price
try:
pay(50, 100)
except InsufficientFundsError as e:
print("支付失败:", e)
自定义异常让调用方可以用 except 你的异常 精准捕获,而不是匹配一串魔法字符串。
6. 调试三板斧
定位问题靠三件套:print 打点、logging 留痕、traceback 看堆栈。
import logging
import traceback
logging.basicConfig(level=logging.DEBUG)
def process(items):
logging.debug(f"收到 {len(items)} 条数据") # 过程打点
result = []
for i, item in enumerate(items):
try:
result.append(item * 2)
except TypeError:
logging.warning(f"第 {i} 条数据异常: {item!r}")
return result
process([1, "x", 3])
# 打印完整堆栈
try:
1 / 0
except ZeroDivisionError:
traceback.print_exc()
还不行就上 pdb:在可疑行前加 breakpoint(),运行到那里可单步执行、查看变量。
💡 学习建议:把 6 种常见异常各制造一次并捕获处理,练到"看到异常名就知道哪里错了"。记住一条原则:except 要具体,别写裸的 except: 吞掉所有错误——那会让 bug 藏得更深。