Python pdb 调试工具详解
pdb是 Python 标准库自带的命令行调试器(Python Debugger),无需安装任何第三方库即可使用。 它可以在程序运行时暂停、逐行执行、查看和修改变量、检查调用栈,是定位 bug 的利器。
1. pdb 是什么 / 适用场景
pdb 全称 Python Debugger,位于标准库 pdb 模块中,随 Python 一起发布。
| 调试方式 | 优点 | 缺点 |
|---|---|---|
print() 打印 | 简单、零成本 | 需要改代码、反复重跑、信息量大时混乱 |
| pdb | 无需安装、可交互、能改运行时状态、适合服务器/远程环境 | 命令行操作、需要记命令 |
| IDE 图形调试(PyCharm、VSCode) | 界面友好、鼠标操作 | 依赖 IDE、远程/容器环境配置麻烦 |
适合使用 pdb 的场景:
- 服务器、Docker 容器、SSH 远程环境,无法使用图形调试器
- 想快速定位某个变量在某一刻的值
- 程序抛出异常后,想回到崩溃现场检查状态
- 学习 Python 执行流程,观察每一步的变化
2. 三种启动方式
2.1 命令行启动(无需改代码)
直接让 Python 以调试模式运行脚本,程序会停在第一行之前:
python -m pdb script.py
启动后进入 (Pdb) 交互提示符,输入命令即可调试。
好处:不用修改源码;坏处:从头开始,需要自己走到目标位置。
2.2 代码内断点 breakpoint()(Python 3.7+ 推荐)
在想要暂停的位置插入 breakpoint():
def calc(a, b):
result = a / b
breakpoint() # 程序运行到这里会停下,进入 pdb
return result
calc(10, 2)
运行 python script.py,程序会在 breakpoint() 处暂停。这是官方推荐的写法,等价于 import pdb; pdb.set_trace(),但更简洁,且可通过环境变量统一控制(见第 7 节)。
2.3 传统方式 pdb.set_trace()
在旧代码或需要显式导入时使用:
import pdb
def calc(a, b):
result = a / b
pdb.set_trace() # 效果与 breakpoint() 相同
return result
注意:
breakpoint()与pdb.set_trace()行为一致,新代码优先用breakpoint()。
3. 核心命令速查表
进入 (Pdb) 提示符后,输入以下命令(括号内为简写)。直接回车会重复上一条命令。
| 命令 | 简写 | 含义 |
|---|---|---|
help | h | 查看帮助;h 命令 查看某命令详情 |
list | l | 显示当前行附近的源码 |
longlist | ll | 显示当前函数的全部源码 |
next | n | 执行下一行(不进入函数内部) |
step | s | 执行下一行(进入函数内部) |
return | r | 一直执行到当前函数返回 |
continue | c | 继续执行,直到下一个断点或程序结束 |
until | unt | 执行到当前行号更大的行(常用于跳出循环) |
jump | j | 跳转到指定行(谨慎使用) |
where | w | 打印当前调用栈(当前位置) |
up | u | 切换到上层栈帧 |
down | d | 切换到下层栈帧 |
break | b | 设置断点 |
tbreak | — | 设置临时断点(命中一次后自动删除) |
clear | cl | 删除断点 |
print | p | 打印表达式的值 |
pp | — | 美化打印(pretty print) |
display | — | 持续显示某表达式的值 |
undisplay | — | 取消持续显示 |
args | a | 打印当前函数的参数 |
whatis | — | 打印表达式的类型 |
source | — | 打印对象的源代码 |
interact | — | 进入交互式解释器(可执行任意 Python 代码) |
quit | q | 退出调试并终止程序 |
! | — | 执行一条 Python 语句,如 !x = 10 |
4. 断点管理
4.1 设置断点
(Pdb) b 15 # 在当前文件第 15 行设置断点
(Pdb) b script.py:15 # 指定文件和行号
(Pdb) b my_func # 在函数 my_func 的第一行设置断点
(Pdb) b # 不带参数:列出所有断点
4.2 条件断点
只在条件成立时才暂停,适合调试循环中的特定情况:
(Pdb) b 20, i == 5 # 第 20 行,仅当 i == 5 时暂停
(Pdb) b 30, len(data) > 100
4.3 临时断点
命中一次后自动删除,适合只想停一次的场景:
(Pdb) tbreak 42
4.4 查看与删除断点
(Pdb) b # 列出所有断点,如 Num 1 file line
(Pdb) cl 1 # 删除编号为 1 的断点
(Pdb) cl # 不带参数:删除所有断点(会二次确认)
断点编号(
Num)由 pdb 自动分配,删除时用的是这个编号,不是行号。
5. 单步执行与调用栈
5.1 单步执行
(Pdb) n # next:执行下一行,不进入函数
(Pdb) s # step:执行下一行,若遇到函数调用则进入
(Pdb) r # return:执行到当前函数返回
(Pdb) c # continue:继续运行到下一个断点
(Pdb) unt # until:运行到当前行之后的行(快速跳出循环)
n 与 s 的区别:
x = 1
y = add(x, 2) # 在这一行,n 会直接执行完 add 得到 y;s 会进入 add 内部
5.2 查看调用栈
(Pdb) w # 打印调用栈,箭头 -> 表示当前所在帧
示例输出:
/app/main.py(20)<module>()
-> result = calc(10, 0)
/app/main.py(8)calc()
-> return a / b
(Pdb)
5.3 切换栈帧
在崩溃点想查看调用者的变量时使用:
(Pdb) u # up:切换到上层(调用者)栈帧
(Pdb) d # down:切换回下层
6. 查看与修改变量
6.1 打印变量
(Pdb) p x # 打印 x 的值
(Pdb) p x + y # 打印表达式结果
(Pdb) pp data # 美化打印(适合字典、列表等复杂结构)
(Pdb) whatis x # 查看 x 的类型
6.2 持续跟踪
display 让变量在每次暂停时自动显示,省去反复输入 p:
(Pdb) display i # 每次停下都显示 i 的值
(Pdb) display # 列出当前所有 display 表达式
(Pdb) undisplay 1 # 取消编号为 1 的显示
6.3 修改变量
用 ! 前缀执行任意 Python 语句,可以临时改变量、调用函数:
(Pdb) !x = 100 # 修改 x 的值
(Pdb) !import json # 执行导入
(Pdb) p x # 验证修改结果:100
修改运行时变量是 pdb 的强大之处:可以在不重启程序的情况下验证修复思路。
6.4 查看函数参数
(Pdb) a # 打印当前函数的参数及值
7. breakpoint() 与 PYTHONBREAKPOINT
breakpoint() 的实际行为由环境变量 PYTHONBREAKPOINT 控制:
# 默认:调用 pdb
python script.py
# 禁用所有 breakpoint()(生产环境常用)
PYTHONBREAKPOINT=0 python script.py
# 指定其他调试器(需已安装,如 ipdb)
PYTHONBREAKPOINT=ipdb.set_trace python script.py
在 Windows PowerShell 中设置环境变量:
$env:PYTHONBREAKPOINT = "0"
python script.py
因为可以一键禁用,
breakpoint()比硬编码的pdb.set_trace()更适合留在代码中。
8. 进阶技巧
8.1 异常后调试(post-mortem)
程序抛出未捕获异常后,直接进入崩溃现场:
import pdb
try:
risky_operation()
except Exception:
pdb.post_mortem() # 停在异常发生的栈帧
命令行方式,脚本崩溃后自动进入调试:
python -m pdb script.py
# 程序因异常停止后:
(Pdb) pm # post-mortem,跳到异常发生处
pm是排查「程序莫名崩溃」最快的手段,无需事先设断点。
8.2 进入交互式解释器 interact
需要执行多行语句或复杂逻辑时:
(Pdb) interact
>>> x = 1
>>> y = 2
>>> x + y
3
>>> Ctrl-D # 退出交互模式,回到 (Pdb)
8.3 断点自动命令 commands
给断点绑定一组命令,命中时自动执行:
(Pdb) b 25
(Pdb) commands 1
(com) p i
(com) p total
(com) c
(com) end
这样每次运行到第 25 行都会自动打印 i 和 total 并继续,适合日志式调试。
8.4 执行任意模块
不写脚本也能调试,直接用 -m 运行模块:
python -m pdb -m mypackage.mymodule
9. 完整实战示例
下面用一个有 bug 的小脚本,演示完整的 pdb 调试流程。
# bug_demo.py
def average(numbers):
total = 0
for n in numbers:
total += n
return total / len(numbers) # 当 numbers 为空时会除零
def main():
data = [10, 20, 30]
print("平均分:", average(data))
empty = []
print("空列表平均:", average(empty)) # 这里会崩溃
if __name__ == "__main__":
main()
步骤 1:用 post-mortem 定位崩溃点
python -m pdb bug_demo.py
程序崩溃后进入 pdb:
> /app/bug_demo.py(5)average()
-> return total / len(numbers)
(Pdb) p numbers
[]
(Pdb) p len(numbers)
0
(Pdb) p total
0
立刻定位到:numbers 是空列表,len(numbers) 为 0,导致 ZeroDivisionError。
步骤 2:用条件断点观察循环
假设问题更隐蔽,需要观察循环过程:
(Pdb) b 4, n == 30 # 仅当 n == 30 时在第 4 行暂停
(Pdb) c
(Pdb) p total # 查看累加值
(Pdb) display n
(Pdb) n # 单步执行,每次自动显示 n
步骤 3:临时修改变量验证思路
(Pdb) !numbers = [1, 2, 3] # 临时替换为有效数据
(Pdb) p average(numbers) # 验证函数逻辑本身正确
6.0
步骤 4:修复代码
确认逻辑无误后,给函数加上空列表保护:
def average(numbers):
if not numbers:
return 0 # 空列表直接返回 0
total = 0
for n in numbers:
total += n
return total / len(numbers)
重新运行,程序正常输出,bug 修复完成。
10. 常见问题 / 速记小结
Q:如何退出 pdb?
q(quit,会终止程序);若想继续运行用 c。
Q:n 和 s 老是记混?
next = 下一步(不进函数);step = 步进(进函数)。想进函数就用 s。
Q:断点设了但没停?
检查行号是否对应当前执行文件;条件断点的条件是否始终为 False;断点是否被 cl 删除。
Q:生产环境担心 breakpoint() 生效?
设置 PYTHONBREAKPOINT=0 即可全局禁用。
Q:如何不重新运行就修 bug?
用 !变量 = 新值 临时修改运行时状态,验证修复思路后再改源码。
常用命令速记
b / tbreak 设断点 / 临时断点
l / ll 看代码
n / s 下一行 / 步入
r / c 返回 / 继续
w / u / d 栈 / 上 / 下
p / pp / ! 打印 / 美化打印 / 执行语句
display 持续跟踪变量
q 退出
与本文相关的基础语法可参考 Python 基础语法。