Skip to content

第 10 章 异常处理与调试

学习目标

  • 理解异常的概念,能读懂 traceback
  • 认识常见异常类型
  • try / except / else / finally 处理异常
  • 掌握 print 调试与最小复现的排查方法

10.1 什么是异常

程序运行中出现的错误叫异常。第 1 章起我们见过的报错(NameErrorTypeError 等),都是异常。异常发生时,解释器打印 traceback 并停止程序——除非我们用 try 提前拦截。

回顾 traceback 的读法(第 1 章「读报错三步」):

Traceback (most recent call last):
  File "demo.py", line 2, in <module>
    print(1 / 0)
ZeroDivisionError: division by zero

最后一行是异常类型异常信息,上面是出错的文件与行号。先看最后一行,再回代码定位,是排查的基本顺序。

10.2 常见异常类型

异常含义典型场景
NameError名字未定义拼错变量名、变量未赋值
TypeError类型不匹配"a" + 1、参数个数不对
ValueError值不合法int("abc")int("3.14")
IndexError下标越界"hello"[10][1, 2][5]
KeyError键不存在d["b"],字典里没有这个键
ZeroDivisionError除数为 010 / 0
FileNotFoundError文件不存在open("nofile.txt")

能说出「哪个场景对应哪个异常」,是调试能力的基础。

10.3 try / except

try / except 让程序「试着执行一段代码,出错时按指定方式处理」:

python
try:
    result = 10 / 0
except ZeroDivisionError:
    print("除数不能为 0")

输出:

除数不能为 0

结构说明:

  1. try 块里的代码可能出错的部分。
  2. 出错时,跳转到匹配的 except;except 后面写要捕获的异常类型
  3. 没出错时,except 不执行。

as e 拿到异常对象,输出异常信息:

python
try:
    result = 10 / 0
except ZeroDivisionError as e:
    print("捕获到异常:", e)

输出:

捕获到异常: division by zero

一个 try 可以接多个 except,分别处理不同类型的异常:

python
try:
    a = float(input("请输入被除数: "))
    b = float(input("请输入除数: "))
    print(a / b)
except ZeroDivisionError:
    print("除数不能为 0")
except ValueError:
    print("请输入数字")

输入 100 时输出 除数不能为 0;输入 abc 时输出 请输入数字。异常按从上到下的顺序匹配,匹配到第一个就停止。

10.4 else 与 finally

else:没有异常时执行(在 except 之后)。

finally:无论是否异常都执行,通常用来做清理工作(如关闭连接)。

python
try:
    result = 10 / 2
except ZeroDivisionError as e:
    print("捕获:", e)
else:
    print("无异常,结果是", result)
finally:
    print("finally 总是执行")

输出:

无异常,结果是 5.0
finally 总是执行

把上面的 10 / 2 改成 10 / 0 再看:输出会变成 捕获: division by zerofinally 总是执行,else 不执行。

10.5 调试技巧

异常拦截是「防守」,真正定位问题靠调试。三个常用方法:

1. print 调试。 在怀疑的位置打印变量的值,观察哪里与预期不符:

python
def average(scores):
    total = 0
    for s in scores:
        total += s
        print("累加中:", total)   # 临时调试输出
    return total / len(scores)

2. 最小复现。 把出错的代码裁剪到最少、可以独立运行的程度,再运行它。这样排除了无关代码的干扰,也方便把「最小复现代码 + 报错」完整地描述给别人。

3. 二分定位。 在程序中间加一个 print("到这里了") 之类的标记:若输出了,说明错误在后面;否则在前面。反复对半缩小范围。

正式的断点调试(编辑器里点击行号暂停运行、逐步观察)是进阶技能,入门阶段 print 调试足够。

动手实践

任务一:带容错的温度换算。 用户输入非数字时重新询问,而不是崩溃:

python
while True:
    try:
        c = float(input("请输入摄氏温度: "))
        break
    except ValueError:
        print("输入的不是数字,请重新输入")

f = c * 9 / 5 + 32
print(f"{c} 摄氏度等于 {f} 华氏度")

while True + break 的搭配:只有输入合法时才 break 退出循环。

任务二:给第 3 章的计算器加上容错。 处理「除数为 0」和「输入非数字」两种情况:

python
try:
    a = float(input("请输入第一个数: "))
    b = float(input("请输入第二个数: "))
    print(f"{a} / {b} = {a / b}")
except ZeroDivisionError:
    print("除数不能为 0")
except ValueError:
    print("请输入数字")

常见错误

错误写法说明
except:(不带类型)或 except Exception: 包住一切会吞掉所有错误,连程序自身的 bug 也被隐藏,难以排查;尽量只捕获预期会发生的异常类型
把大段代码全塞进 try难以判断错误出在哪一行;让 try 只包住可能出错的少数几行
except 里只 pass异常被静默忽略,问题还会以更隐蔽的方式出现
捕获了异常却不看 eas e 拿到信息有助于判断根因

章末练习

基础

  1. 写程序:读入两个数做除法,捕获 ZeroDivisionError 并输出「除数不能为 0」。
  2. 写程序:尝试 int(input(...)),捕获 ValueError 并输出「请输入整数」。
  3. 指出下列代码会抛出什么异常:
    • print([1, 2][5])
    • print(d["x"])(d 中没有键 "x")
    • open("no.txt")

提高

  1. 写函数 safe_divide(a, b),返回 a / b 的结果;除数为 0 时返回 None
  2. 写程序:循环读入成绩(输入 q 结束),用 try 跳过非数字输入,最后输出所有合法成绩的平均分。

挑战

  1. 读入一个文件名,用 try 尝试打开;文件不存在时提示「文件不存在」,并让用户重新输入,直到成功打开为止。
  2. 用 print 调试法,找出下面代码的错误并修复(不运行,先推理,再运行验证):
    python
    def total_scores(text):
        scores = text.split(",")
        return sum(scores)
    
    print(total_scores("85,92,78"))

章末自测

每题选择一个最佳答案。本书不附答案:完成后交由老师或 AI 老师批改讲解。

  1. 除数为 0 会抛出的异常是?
    • A. ValueError
    • B. ZeroDivisionError
    • C. TypeError
    • D. IndexError
  2. 下标越界会抛出的异常是?
    • A. KeyError
    • B. NameError
    • C. IndexError
    • D. ValueError
  3. 字典键不存在会抛出的异常是?
    • A. KeyError
    • B. IndexError
    • C. TypeError
    • D. FileNotFoundError
  4. int("abc") 会抛出?
    • A. TypeError
    • B. ValueError
    • C. NameError
    • D. KeyError
  5. try 块中代码没有出错时,else 块?
    • A. 执行
    • B. 不执行
    • C. 报错
    • D. 视情况
  6. 无论是否出错都执行的块是?
    • A. try
    • B. except
    • C. else
    • D. finally
  7. except ZeroDivisionError as e:e 是?
    • A. 异常类型
    • B. 异常对象
    • C. 数字
    • D. 文件
  8. 一个 try 可以有?
    • A. 只能一个 except
    • B. 多个 except
    • C. 不能有 except
    • D. 两个 finally
  9. 捕获一切异常的写法是?
    • A. except:
    • B. catch all:
    • C. try except:
    • D. exception:
  10. 关于「最小复现」,下列说法正确的是? A. 让代码尽可能短小并可独立运行 B. 让代码尽量长 C. 删除所有 print D. 只保留报错行