本节摘要:SOURCE 2.3 覆盖 %debug 魔法、pdb.set_trace() 断点、print/logging、unittest 以及「导出到 IDE 调试」的退路。本节按「异常后立刻查」到「主动设断点」再到「可重复测试」三层展开。
阅读完本节,你应当能够:
%debug 并用法 n/c/p/qpdb.set_trace() 主动断点SOURCE 流程:代码抛异常 → 新格输入 %debug → 进入 IPython 调试器,可查看栈帧与局部变量。
调试器常用命令(SOURCE 列出):
| 命令 | 含义 |
|---|---|
| n (next) | 单步,不进入函数 |
| c (continue) | 跑到下一断点或结束 |
| p 变量名 | 打印变量 |
| q (quit) | 退出调试 |
示例:
def divide(a, b): return a / b divide(1, 0) # ZeroDivisionError
下一格:
%debug # 在调试器里 p a, p b,看 b 是否为 0
⚠️ 常见坑:必须先跑出异常再
%debug——它会进入最近一次 traceback。若中间又跑了别的格,栈可能不是你想看的。
SOURCE 示例:
import pdb def my_function(x): y = x * 2 pdb.set_trace() # 执行到此暂停 z = y + 1 return z my_function(5)
Python 3.7+ 可用 breakpoint()(等价且更短)。在 Notebook 里运行到断点格会阻塞内核,直到在终端/调试 UI 输入 continue——JupyterLab 3+ 有可视化调试面板,经典 Notebook 多在文本交互里操作。
SOURCE 承认 print 仍是最常用手段——在关键行 print(f"y = {y}") 快速看状态。数据稍复杂时改用 logging:
import logging logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s") def my_function(x): logging.debug(f"Input x = {x}") y = x * 2 logging.debug(f"y = {y}") return y + 1
logging 可设 DEBUG/INFO 级别、输出到文件——长跑 pipeline 比满屏 print 更易过滤。
SOURCE 给出 unittest 模板,可在独立格验证小函数:
import unittest def add(x, y): return x + y class TestAdd(unittest.TestCase): def test_add_positive(self): self.assertEqual(add(2, 3), 5) def test_add_negative(self): self.assertEqual(add(-2, -3), -5) unittest.main(argv=[""], exit=False)
Notebook 里 exit=False 避免内核被 unittest 退出。更大项目仍建议测试放 .py + pytest,Notebook 只 %run tests。
SOURCE 提到:复杂断点、多线程、大型项目时,nbconvert 成 .py 后在 VS Code / PyCharm 调试可能更顺手。Notebook 适合探索;模块稳定后下沉到包是正常演进。
💡 关键直觉:Notebook 调试优势是「异常现场变量还在内存里」——%debug 立刻 p DataFrame.shape;IDE 优势是断点 UI 与多文件步进。
下一节 2.4 代码风格与可读性 把能跑通的代码变成团队能 review 的形态。
| 场景 | 首选手段 | 原因 |
|---|---|---|
| 异常后想快速看现场 | %debug | 直接进入最近 traceback |
| 知道大概在哪个函数 | breakpoint() | 精确停在指定行 |
| 数据不大、逻辑简单 | print / logging | 零成本 |
| 多步骤 pipeline 出问题 | 每步 assert shape | 快速定位断点在哪 |
| 多线程、复杂断点 | 导出 .py 用 IDE | 可视化步进 |
长 pipeline 里逐格 print 很啰嗦。更省事的方式是在关键转换后断言形状与取值:
df = df.dropna(subset=["price"]) assert df["price"].notna().all(), "price 仍有缺失" assert df.shape[0] > 0, "清洗后没有行"
assert 失败会抛出 AssertionError,配合 %debug 直接进入现场,比翻几十行 print 输出快得多。
别让 try/except 把错误吞掉。推荐记录原始异常并保留堆栈:
import traceback try: result = risky_function(data) except Exception: traceback.print_exc() raise ValueError("risky_function 处理失败,请检查输入列名")
Notebook 里保留了堆栈,后续可以直接 %debug 进到原始异常位置。
在 Notebook 顶部统一 logging.basicConfig(level=logging.INFO),后续所有模块共用同一级别;想深挖再临时改成 DEBUG,不用改业务代码。
pdb 的 break 命令支持条件。在调试器里输入 break <行号>, 条件,例如让循环到第 50 次才停,避免每次迭代都中断:
import pdb for i in range(100): pdb.set_trace() if i == 50 else None # 生产代码中请用条件断点而不是条件 set_trace
更好的写法是 JupyterLab 可视化调试器里设置条件断点,界面输入表达式即可,代码保持干净。
| 命令 | 作用 |
|---|---|
| w | 打印当前调用栈 |
| l | 列出当前行附近源码 |
| s | 单步进入函数 |
| r | 跑到当前函数返回 |
| pp 表达式 | 格式化打印复杂对象 |
调试期记住 w、l、s、r 四条,配合 p,绝大多数问题在十分钟内能定位。
排完一次疑难 bug 后,花两分钟把结论写进 Notebook 结尾的 Markdown 格:现象、根因、修复方式、下次怎么预防。这份"调试日记"会在三个月后帮你避开同样的坑,也比重新读一遍代码快得多。
如果同一个异常反复出现、条件组合复杂、涉及多线程或外部服务,手动 %debug 的效率会明显下降。这时果断把代码下沉到 .py 模块,用 IDE 的可视化调试器断点与监视变量。Notebook 适合快速验证,长难问题交给专业工具,是成熟工程师的分工意识。
假设你运行一个单元格报了 KeyError,但没有 traceback 行号提示到哪一行。推荐的链路是:先在报错信息里定位是哪个字典或 DataFrame 列访问出的问题,再 p 打印那个变量的列名列表核对拼写,最后确认该列是否在清洗步骤中被重命名。这个流程的核心是"从现场证据出发",而不是凭记忆猜。