跳到主要内容

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) 提示符后,输入以下命令(括号内为简写)。直接回车会重复上一条命令。

命令简写含义
helph查看帮助;h 命令 查看某命令详情
listl显示当前行附近的源码
longlistll显示当前函数的全部源码
nextn执行下一行(不进入函数内部)
steps执行下一行(进入函数内部)
returnr一直执行到当前函数返回
continuec继续执行,直到下一个断点或程序结束
untilunt执行到当前行号更大的行(常用于跳出循环)
jumpj跳转到指定行(谨慎使用)
wherew打印当前调用栈(当前位置)
upu切换到上层栈帧
downd切换到下层栈帧
breakb设置断点
tbreak—设置临时断点(命中一次后自动删除)
clearcl删除断点
printp打印表达式的值
pp—美化打印(pretty print)
display—持续显示某表达式的值
undisplay—取消持续显示
argsa打印当前函数的参数
whatis—打印表达式的类型
source—打印对象的源代码
interact—进入交互式解释器(可执行任意 Python 代码)
quitq退出调试并终止程序
!—执行一条 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 基础语法。