程序出错不可怕,可怕的是直接崩溃。异常处理让程序"优雅地失败":该报错时报错,该兜底时兜底。配合调试技巧,你就能从"看到 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 藏得更深。