前面我们学习了使用 Python 自带的 pdb 模块调试程序,也接触了 pdb.set_trace() 这种设置断点的方法。

例如,当服务器巡检程序发现 CPU 使用率异常时,我们可能想知道:程序读取的 CPU 使用率是多少?为什么进入了告警分支?是哪一行代码产生了问题?

如果为了调试而修改源代码、添加断点,调试完成后还要清理这些代码,就会多出一些工作。

其实,Python 提供了另一种更直接的方式:在命令行中启动 pdb 调试器,不修改源代码也能逐步检查程序的执行过程。

这篇文章将从一个完整的示例开始,带你实际操作命令行调试。

准备一个需要调试的 Python 程序

假设我们正在编写一个服务器巡检程序,需要检查 CPU 和内存的使用率。

新建一个名为 server_check.py 的文件,写入以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 模拟服务器的资源使用率
cpu_usage = 72
memory_usage = 88

print("开始检查服务器资源")

# 检查 CPU 使用率
if cpu_usage > 80:
print("CPU 使用率过高")
else:
print("CPU 使用率正常")

# 检查内存使用率
if memory_usage > 80:
print("内存使用率过高")
else:
print("内存使用率正常")

print("服务器检查完成")

这段程序模拟了服务器资源检查的过程。

  • cpu_usage = 72:将 CPU 使用率设置为 72。
  • memory_usage = 88:将内存使用率设置为 88。
  • if cpu_usage > 80:判断 CPU 使用率是否超过 80。
  • if memory_usage > 80:判断内存使用率是否超过 80。
  • 最后一条 print():输出检查完成的信息。

这里的 CPU 和内存使用率都是我们手动设置的模拟数据,并没有真正读取服务器的资源使用情况。

保存文件后,在终端中进入文件所在的目录。

先正常运行一次程序:

1
python server_check.py

预期输出:

1
2
3
4
开始检查服务器资源
CPU 使用率正常
内存使用率过高
服务器检查完成

CPU 使用率为 72,没有超过 80,所以程序输出 CPU 使用率正常。

内存使用率为 88,超过了 80,所以程序输出内存使用率过高。

如果我们希望观察程序是如何得出这些结果的,就可以启动调试器。

使用 python -m pdb 启动调试器

在终端中执行:

1
python -m pdb server_check.py

这条命令可以拆成三个部分理解:

命令含义
python启动 Python 解释器
-m pdb通过模块方式启动 Python 自带的 pdb 调试器
server_check.py指定需要调试的 Python 文件

简单来说,这条命令就是让 Python 使用 pdb 调试器运行 server_check.py。

这里不需要在源代码中添加 import pdb 或 pdb.set_trace()。

第一次启动时,调试器会做什么?

执行命令后,终端通常会显示类似下面的内容:

1
2
3
> /path/to/server_check.py(2)<module>()
-> cpu_usage = 72
(Pdb)

示例中的 /path/to/server_check.py 只是占位路径,实际显示的是你自己的文件路径。

这里的 (2) 表示调试器当前关联的是文件第 2 行。由于第 1 行是注释,调试器通常会停在第一条可执行语句 cpu_usage = 72 上。

关键是:首次启动时,调试器会先让你获得控制权,而不是直接把整个脚本运行完。 当前显示的代码通常还没有执行。

我们逐项理解终端中的信息。

1
> /path/to/server_check.py(2)<module>()

这部分表示当前调试的文件、代码行号,以及正在执行的代码所属的作用域。<module>() 表示模块顶层代码,而不是某个自定义函数。

1
-> cpu_usage = 72

箭头 -> 指向当前准备执行的代码。

此时,赋值语句还没有执行,因此变量 cpu_usage 尚未通过这行代码创建。

1
(Pdb)

这是调试器的命令提示符,表示程序已经暂停在调试状态,正在等待我们输入命令。

这里还有一个容易混淆的地方:pdb 不会因为停在第一条语句前,就自动把所有变量准备好。变量需要等到对应的赋值语句真正执行之后才能读取。

从首次暂停开始,完整调试程序

下面按照实际操作顺序,观察程序如何从启动、暂停、逐行执行,一直到结束。

查看尚未创建的变量

当终端显示:

1
(Pdb)

时,输入:

1
p cpu_usage

通常会得到类似结果:

1
*** NameError: name 'cpu_usage' is not defined

原因很简单:程序还没有执行 cpu_usage = 72,变量自然还不存在。

这不是程序出错,而是我们查看变量的时间太早了。

执行第一条赋值语句

输入:

1
n

n 是 next 的缩写,表示执行当前语句,然后停在下一条将要执行的语句处。

当前语句:

1
cpu_usage = 72

执行完成后,调试器会停在后续代码的位置,通常是下一条赋值语句:

1
memory_usage = 88

这时再输入:

1
p cpu_usage

预期输出:

1
72

说明变量已经创建,值为 72。

继续执行下一行:

1
n

现在 memory_usage = 88 也已执行。

输入:

1
p memory_usage

预期输出:

1
88

通过这种方式,我们可以在程序运行过程中检查变量,而不必额外添加 print() 语句。

观察程序执行输出语句

继续输入:

1
n

多次执行 n,直到调试器停在:

1
print("开始检查服务器资源")

输入:

1
n

程序执行这条输出语句,终端显示:

1
开始检查服务器资源

调试器随后会停在接下来将要执行的语句处。

请注意,调试器显示的箭头表示当前准备执行的代码,而不是最近已经执行完的代码。因此,输入一次 n 后,看到箭头移动到下一条语句是正常现象。

观察 CPU 判断分支

继续使用 n,直到调试器停在:

1
if cpu_usage > 80:

先查看变量:

1
p cpu_usage

输出:

1
72

再查看条件表达式:

1
p cpu_usage > 80

输出:

1
False

False 表示条件不成立。

因为 72 不大于 80,程序不会执行 if 后面的告警语句,而是进入 else 分支。

输入:

1
n

调试器会继续按照程序的实际执行路径移动。当箭头指向:

1
print("CPU 使用率正常")

再输入:

1
n

终端就会输出:

1
CPU 使用率正常

通过这个过程,我们不仅知道最终输出了什么,还能看到程序为什么会产生这样的结果。

观察内存判断分支

继续使用 n,直到调试器停在:

1
if memory_usage > 80:

输入:

1
p memory_usage

输出:

1
88

再检查判断条件:

1
p memory_usage > 80

输出:

1
True

True 表示条件成立。

因为内存使用率 88 大于 80,程序会执行 if 分支中的告警语句。

继续输入:

1
n

当箭头指向:

1
print("内存使用率过高")

再次输入:

1
n

终端输出:

1
内存使用率过高

通过这几步,我们观察到了两种不同的条件判断结果:

  • CPU 使用率为 72,判断结果为 False,进入 else 分支。
  • 内存使用率为 88,判断结果为 True,进入 if 分支。

这就是调试器最实用的地方之一:不仅能查看变量,还能跟踪程序实际走了哪条执行路径。

使用 c 继续运行程序

如果不想再逐行执行,可以输入:

1
c

c 是 continue 的缩写,表示继续运行程序,直到程序结束或遇到其他调试暂停位置。

假设前面的代码已经执行完毕,调试器当前停在最后一条输出语句:

1
print("服务器检查完成")

输入 c 后,程序会继续执行,并输出:

1
服务器检查完成

如果你在刚启动调试器时就输入 c,则程序会从当前暂停的位置继续执行,通常会一次性执行完剩余代码。

程序结束后为什么可能再次运行?

这是使用 python -m pdb 时尤其值得注意的地方。

当程序正常结束后,pdb 默认会重新启动被调试的程序,以便用户可以再次进行调试。因此,你可能看到类似这样的终端输出:

1
2
3
4
5
服务器检查完成
The program finished and will be restarted
> /path/to/server_check.py(2)<module>()
-> cpu_usage = 72
(Pdb)

其中,英文提示表示程序已经结束,调试器准备重新启动它。

紧接着再次出现的代码位置和 (Pdb) 提示符,代表新一轮调试已经开始,程序又停在第一条可执行语句附近。

这不代表程序陷入了死循环,也不代表 n 或 c 失效,而是 pdb 默认的重新启动行为。

如果只是想完成一次调试,不需要重新运行程序,可以使用下面介绍的 q 命令退出。

使用 q 退出调试器

当终端重新出现 (Pdb) 提示符时,输入:

1
q

q 是 quit 的缩写,用于退出调试器。

退出后,就会返回普通的终端命令提示符。

这样,一次完整的命令行调试就结束了。

常用调试命令回顾

命令作用使用场景
p 表达式查看表达式的值查看变量、判断条件的结果
n执行下一行,不进入被调用函数内部逐行调试逐步观察程序执行
s执行当前语句,并在函数调用时进入函数内部检查函数内部逻辑
c继续运行程序完成检查后继续执行
q退出调试器结束本次调试

例如:

1
2
3
4
5
6
7
p cpu_usage
p memory_usage
p cpu_usage > 80
n
s
c
q

这些命令都在 (Pdb) 提示符下输入,而不是在普通的 Windows 命令提示符或 Linux Shell 中单独执行。

其中,s 是 step 的缩写。它与 n 的主要区别在于:如果当前代码调用了函数,s 可以进入函数内部继续调试,而 n 通常会执行完这次函数调用,再停在调用位置之后的代码处。

动手练习

请把下面的代码保存为 server_check.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cpu_usage = 65
memory_usage = 92

print("开始检查服务器资源")

if cpu_usage > 80:
print("CPU 使用率过高")
else:
print("CPU 使用率正常")

if memory_usage > 80:
print("内存使用率过高")
else:
print("内存使用率正常")

print("服务器检查完成")

按照以下步骤练习:

  • 使用 python -m pdb server_check.py 启动调试器。
  • 在首次暂停时,尝试查看 cpu_usage,理解为什么它还没有定义。
  • 使用 n 执行赋值语句。
  • 使用 p cpu_usage 和 p memory_usage 查看变量值。
  • 使用 p cpu_usage > 80 和 p memory_usage > 80 查看判断结果。
  • 使用 n 观察程序如何进入不同的条件分支。
  • 使用 c 继续运行程序。
  • 如果程序结束后调试器重新启动,请使用 q 退出。

参考结果:

1
2
3
4
65
92
False
True

这四个结果分别对应两个变量的值,以及两个条件判断的结果。

如果你能够独立完成这些操作,就已经掌握了使用命令行启动调试器、查看变量和跟踪条件分支的基本方法。

写在最后

本篇介绍了如何使用 python -m pdb script.py 在命令行中启动 Python 调试器,并通过服务器资源检查示例,演示了首次暂停、查看变量、逐行执行、检查条件分支、继续运行和退出调试器的完整过程。

需要记住两个关键点:

  • 首次启动时,pdb 通常会在第一条可执行语句处暂停,等待我们输入调试命令。
  • 程序结束后,pdb 默认可能重新启动脚本;如果不需要再次调试,可以使用 q 退出。

下一篇将继续学习 pdb 的常用调试命令,进一步掌握设置断点、查看调用栈和控制程序执行的方法。