2.3 调试技巧


2.3 调试技巧

本节摘要:SOURCE 2.3 覆盖 %debug 魔法、pdb.set_trace() 断点、print/logging、unittest 以及「导出到 IDE 调试」的退路。本节按「异常后立刻查」到「主动设断点」再到「可重复测试」三层展开。

本节地图

阅读完本节,你应当能够:

  1. 异常后在下一格执行 %debug 并用法 n/c/p/q
  2. 在代码中插入 pdb.set_trace() 主动断点
  3. 用 logging 模块分级输出替代散落 print
  4. 在 Notebook 格内写 unittest 用例快速回归

一、%debug:异常后的交互调试器

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。若中间又跑了别的格,栈可能不是你想看的。

二、pdb.set_trace():预定断点

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 多在文本交互里操作。

三、print 与 logging

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 更易过滤。

四、单元测试:unittest 片段

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

五、何时导出到 IDE

SOURCE 提到:复杂断点、多线程、大型项目时,nbconvert 成 .py 后在 VS Code / PyCharm 调试可能更顺手。Notebook 适合探索;模块稳定后下沉到包是正常演进。

💡 关键直觉:Notebook 调试优势是「异常现场变量还在内存里」——%debug 立刻 p DataFrame.shape;IDE 优势是断点 UI 与多文件步进。

要点串联

  • %debug:最近一次异常后进入
  • pdb.set_trace() / breakpoint():主动断点
  • logging:可分级、可落盘
  • unittest + exit=False:格内快速回归
  • 复杂场景:导出 .py 用 IDE

下一节 2.4 代码风格与可读性 把能跑通的代码变成团队能 review 的形态。

调试场景对照表

场景 首选手段 原因
异常后想快速看现场 %debug 直接进入最近 traceback
知道大概在哪个函数 breakpoint() 精确停在指定行
数据不大、逻辑简单 print / logging 零成本
多步骤 pipeline 出问题 每步 assert shape 快速定位断点在哪
多线程、复杂断点 导出 .py 用 IDE 可视化步进

用 assert 在 Notebook 里做"路标"

长 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 进到原始异常位置。

日志级别选择

  • DEBUG:开发期逐行细节,长 pipeline 会刷屏。
  • INFO:每阶段完成一行摘要,日常跑批推荐。
  • WARNING:异常但可继续(如缺失值比例超过阈值)。
  • ERROR:必须人工介入。

在 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 打印那个变量的列名列表核对拼写,最后确认该列是否在清洗步骤中被重命名。这个流程的核心是"从现场证据出发",而不是凭记忆猜。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U