1
2
3
4
5
6
7
作者:李晓辉

联系方式:

1. 微信:Lxh_Chat

2. 邮箱:939958092@qq.com
  1. Python 系列(一) 从零开始认识 Python,入门其实没那么难
  2. Python 系列(二) 学 Python 的第一步,把开发环境搭建好
  3. Python 系列(三) 基础语法避坑指南,缩进、大小写、续行与注释
  4. Python 系列(四) Python 输入输出详解,print()、input() 与常见避坑指南
  5. Python 系列(五) 数值类型与运算符详解,整除、浮点精度与进制转换实战
  6. Python 系列(六) 字符串处理实战,查找、替换、切片与日志分析
  7. Python 系列(七) 类型转换详解,int()、float()、str() 与金额计算避坑
  8. Python 系列(八) Python 流程控制入门,掌握 if、elif 和 else
  9. Python 系列(九) Python 逻辑运算符详解,and、or、not 一篇搞懂
  10. Python 系列(十) while、for 循环实战,彻底搞懂 range、break 与 continue
  11. Python 系列(十一) list、tuple、set、dict,到底该怎么选?
  12. Python 系列(十二) List 列表实战,搞定数据增删改查与批量处理
  13. Python 系列(十三) Tuple 元组实战,搞懂不可变特性与 enumerate() 遍历
  14. Python 系列(十四) Set 集合实战,搞定数据去重与集合运算
  15. Python 系列(十五) Dict 字典详解,掌握快速查找与键值操作
  16. Python 系列(十六) sort() 和 sorted() 详解,掌握数据排序技巧
  17. Python 系列(十七) 自定义函数入门,让重复代码一次编写多次使用
  18. Python 系列(十八) 函数参数详解,搞懂位置参数、关键字参数和默认参数
  19. Python 系列(十九) 从 *args 到 **kwargs,让函数调用更灵活
  20. Python 系列(二十) 变量作用域是什么?一文搞懂 global 和 nonlocal
  21. Python 系列(二十一) 搞懂变量、对象和引用,理解函数参数传递机制
  22. Python 系列(二十二) map() 和 filter() 详解,轻松实现批量数据处理与筛选
  23. Python 系列(二十三) 嵌套函数与 lambda 表达式详解,搞懂作用域与闭包
  24. Python 系列(二十四) 搞懂递归函数,从基线条件到实际应用
  25. Python 系列(二十五) 聊聊模块与 import,让代码告别重复编写
  26. Python 系列(二十六) Python 模块入门,从 import 到模块复用
  27. Python 系列(二十七) Python dir() 函数详解,快速查看模块中的函数与属性
  28. Python 系列(二十八) 不会用 Python 标准库?教你快速查找模块和函数
  29. Python 系列(二十九) 搞懂 Python sys 模块,让脚本更适合自动化运维
  30. Python 系列(三十) 为什么 0.1 + 0.2 不等于 0.3?彻底搞懂 Decimal
  31. Python 系列(三十一) 搞懂 datetime 日期时间,轻松处理时区与时间计算
  32. Python 系列(三十二) 面向对象编程入门,从零理解类、对象、封装与继承
  33. Python 系列(三十三) 什么是类和对象?零基础理解 Python class
  34. Python 系列(三十四) Python 属性详解,搞懂类属性、实例属性和 setter
  35. Python 系列(三十五) 学会特殊方法,让对象打印出你想要的内容
  36. Python 系列(三十六) 多个对象如何共享数据?彻底搞懂类变量
  37. Python 系列(三十七) 搞懂父类、子类和方法重写,真正理解继承与多态
  38. Python 系列(三十八) 如何判断数据类型?三个内置函数一次讲明白
  39. Python 系列(三十九) 搞懂异常模型与常见异常类型,读懂程序报错
  40. Python 系列(四十) 从零学会异常处理,掌握 try、except、else 和 finally
  41. Python 系列(四十一) ValueError、TypeError 到底是什么?一文搞懂常见异常
  42. Python 系列(四十二) ValueError、TypeError 到底是什么?一文搞懂常见异常
  43. Python 系列(四十三) 别只会处理异常!学会自定义异常,让程序更容易维护
  44. Python 系列(四十四) 从零认识 assert 断言,让程序学会检查自己
  45. Python 系列(四十五) 使用 sys 模块处理标准输入与输出
  46. Python 系列(四十六) 使用 open() 读取和写入文件
  47. Python 系列(四十七) 学会读写文本文件,让程序保存运行结果
  48. Python 系列(四十八) 搞懂 bytes、bytearray 和二进制读写
  49. Python 系列(四十九) 使用 seek() 和 tell() 灵活控制文件读取位置
  50. Python 系列(五十) 掌握 os 和 os.path,实现文件与目录自动化管理
  51. Python 系列(五十一) 掌握列表推导式,轻松实现列表生成与数据筛选
  52. Python 系列(五十二) 掌握字典推导式,让字典创建更简单
  53. Python 系列(五十三) 从零理解生成器,轻松处理大量数据
  54. Python 系列(五十五) 使用 zip() 同时遍历多个集合
  55. Python 系列(五十六) 使用 re 模块进行正则表达式匹配
  56. Python 系列(五十七) 理解正则表达式的字符类和量词
  57. Python 系列(五十八) 搞懂正则表达式中的分组与量词
  58. Python 系列(五十九) 学会正则捕获组,轻松提取文本数据
  59. Python 系列(六十) 掌握 re 模块的常用函数
  60. Python 系列(六十一) JSON 是什么?语法规则、数据类型与校验方法
  61. Python 系列(六十二) 使用 pdb 逐步调试程序,定位代码问题
  62. Python 系列(六十三) 在交互式 Python shell 中启动调试器
  63. Python 系列(六十四) 从零开始学习 pdb 命令行调试
  64. Python 系列(六十五) 使用 pdb 设置断点并检查程序执行上下文

编写 Python 程序时,即使代码没有语法错误,也可能出现结果不符合预期的情况。例如,服务器巡检脚本明明读取到了 CPU 使用率,却没有输出预期的告警信息。遇到这种问题,我们就需要检查程序的执行过程,找出问题出现在哪里。

前面我们学习了 Python 标准库 pdb,知道可以通过 pdb.set_trace() 在程序中设置调试入口,也可以使用 python -m pdb 从命令行启动调试器。

进入调试器之后,应该怎样一步步检查程序?如何查看变量、进入函数、设置断点,以及让程序继续执行?

本篇文章将通过一个完整的 Linux 服务器巡检示例,统一介绍命令行调试流程和常用的 pdb 调试命令。

准备需要调试的 Python 程序

假设我们正在编写一个服务器巡检程序,需要根据 CPU 使用率判断服务器是否存在性能风险。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def calculate_cpu(cpu_usage):
print("正在检查 CPU 使用率")

result = cpu_usage >= 80

return result


def check_server(cpu_usage):
print("开始检查服务器")

result = calculate_cpu(cpu_usage)

if result:
print("警告:CPU 使用率过高")
else:
print("CPU 使用率正常")

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


check_server(85)
print("巡检程序执行结束")

这段代码包含两个函数:

  • calculate_cpu():判断 CPU 使用率是否达到 80%。
  • check_server():调用 calculate_cpu(),根据返回结果输出告警信息。

程序最后执行 check_server(85),传入 CPU 使用率 85。

正常情况下,运行程序会得到以下输出:

1
2
3
4
5
开始检查服务器
正在检查 CPU 使用率
警告:CPU 使用率过高
服务器检查完成
巡检程序执行结束

这段程序虽然很简单,但包含了函数调用、变量赋值、条件判断和函数返回等常见操作,适合用来练习调试器命令。

从命令行启动 pdb 调试器

pdb 是 Python 标准库提供的调试器,不需要额外安装第三方软件。

进入保存 cpu_check.py 的目录,在终端中执行:

1
python -m pdb cpu_check.py

如果当前环境使用 python3 命令,则执行:

1
python3 -m pdb cpu_check.py

这里的 -m pdb 表示让 Python 以模块运行方式启动 pdb,并由它调试后面的 cpu_check.py 文件。

首次启动时,调试器通常会显示类似下面的信息:

1
2
3
> cpu_check.py(1)<module>()
-> def calculate_cpu(cpu_usage):
(Pdb)

这里需要理解几个关键内容:

  • cpu_check.py:当前正在调试的 Python 文件。
  • (1):当前显示的源代码行号。
  • <module>:表示当前位于模块顶层代码中,还没有进入某个函数。
  • ->:表示当前显示的待执行代码行。
  • (Pdb):表示调试器正在等待输入命令。

使用 python -m pdb cpu_check.py 启动时,调试器默认会先在模块第一行进入调试状态,而不是直接把程序执行到底。

因此,首次启动后,我们可以先检查代码、设置断点,也可以直接使用 n、s 等命令开始调试。

后续所有调试命令都在 (Pdb) 提示符下输入,而不是写进 Python 源代码文件。

使用 l 命令查看源代码

刚进入调试器时,我们可能还不知道程序会执行哪些代码。

此时可以输入:

1
l

l 是 list 的缩写,用于查看当前调试位置附近的源代码。

调试器会显示类似下面的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(Pdb) l
1 -> def calculate_cpu(cpu_usage):
2 print("正在检查 CPU 使用率")
3
4 result = cpu_usage >= 80
5
6 return result
7
8
9 def check_server(cpu_usage):
10 print("开始检查服务器")
11
12 result = calculate_cpu(cpu_usage)
13
14 if result:
15 print("警告:CPU 使用率过高")
16 else:
17 print("CPU 使用率正常")
(Pdb)

实际显示的行号和格式可能因代码内容而有所不同。

-> 表示调试器当前关注的代码位置。刚启动时,箭头指向第一行函数定义。此时函数还没有执行,程序也还没有调用 check_server(85)。

如果想查看更多代码,可以继续输入:

1
l

也可以指定希望查看的行号,例如:

1
l 9

这样就可以查看第 9 行附近的源代码。

使用 b 命令设置断点

逐行执行整个程序虽然可以了解执行过程,但当代码很多时,这种方式会比较耗时。

例如,我们只关心 check_server() 函数中的条件判断,不想每次都从第一行开始逐步执行。这时就可以设置断点。

断点可以理解为程序中的一个暂停位置。程序运行到断点对应的位置时,调试器会暂停执行,让我们检查变量或执行其他调试命令。

在指定行设置断点

在前面的程序中,第 14 行是条件判断:

1
if result:

我们希望程序运行到这里时暂停,可以输入:

1
b 14

这里的 b 是 break 的缩写,14 表示希望设置断点的源代码行号。

调试器会显示类似下面的结果:

1
2
3
(Pdb) b 14
Breakpoint 1 at cpu_check.py:14
(Pdb)

这表示已经成功设置了编号为 1 的断点,位置是 cpu_check.py 的第 14 行。

需要注意,设置断点时应选择可以执行的代码位置。对于空行、注释行或其他不可执行的位置,调试器可能无法按预期暂停。

查看已经设置的断点

断点设置完成后,可以输入:

1
b

或者使用完整命令:

1
break

调试器会列出当前的断点信息,输出类似:

1
2
3
4
(Pdb) b
Num Type Disp Enb Where
1 breakpoint keep yes at cpu_check.py:14
(Pdb)

这里最值得关注的是:

  • Num:断点编号,后续可以通过编号管理断点。
  • Enb:断点是否启用,yes 表示启用。
  • Where:断点所在的位置。

断点编号不一定永远是 1,它取决于设置断点的顺序。

通过函数名设置断点

除了指定行号,也可以通过函数名设置断点。

例如:

1
b calculate_cpu

这表示在 calculate_cpu() 函数入口设置断点。

调试器会找到这个函数对应的位置,并在执行到该函数入口时暂停。

这种方式适合我们只关心某个函数的执行过程,而不想记住具体行号的情况。

禁用和启用断点

如果暂时不想让某个断点生效,但又不想删除它,可以使用 disable 命令。

例如,禁用编号为 1 的断点:

1
disable 1

如果之后需要重新启用,可以输入:

1
enable 1

其中,1 是前面显示的断点编号。

删除断点

如果不再需要某个断点,可以通过 clear 命令删除。

例如:

1
clear 1

如果调试器要求确认删除,根据提示输入 y 并按回车即可。

也可以使用:

1
clear

按照调试器提示清除所有断点。

不同操作的区别如下:

命令作用
b 14在第 14 行设置断点
b calculate_cpu在指定函数入口设置断点
b查看断点列表
disable 1禁用编号为 1 的断点
enable 1启用编号为 1 的断点
clear 1删除编号为 1 的断点

设置断点后,还需要让程序继续执行,程序才有机会运行到断点所在的位置。接下来,我们就通过 c 命令完成这个过程。

使用 c 命令运行到断点

在刚才的命令行调试过程中,我们已经设置了第 14 行断点。

此时输入:

1
c

c 是 continue 的缩写,表示继续执行程序。

调试器会从当前暂停的位置继续运行,直到遇到下一个有效断点,或者程序正常结束、发生异常等情况。

由于我们在第 14 行设置了断点,因此程序会依次执行函数定义、调用 check_server(85),然后进入 calculate_cpu() 函数完成 CPU 使用率判断。

随后,程序进入 check_server() 函数,在执行到第 14 行时暂停。

调试器会显示类似下面的信息:

1
2
3
4
5
开始检查服务器
正在检查 CPU 使用率
> cpu_check.py(14)check_server()
-> if result:
(Pdb)

这时我们可以确认,程序已经运行到了目标位置。

注意,断点通常会让程序暂停在对应语句即将执行的位置。因此,虽然我们已经看到前面的输出,但条件判断以及后续分支代码还没有执行。

断点最重要的用途,就是让程序自动运行到我们关心的位置,而不是要求我们手动执行每一行代码。

使用 p 命令查看变量

程序暂停在第 14 行时,我们想知道条件判断为什么会进入某个分支,就需要检查变量的值。

p 是 print 的缩写,可以计算并显示变量或表达式的结果。

输入:

1
p result

预期输出:

1
True

这说明当前 check_server() 函数中的 result 值为 True。

我们还可以继续查看:

1
p cpu_usage

预期输出:

1
85

这说明当前函数接收到的 CPU 使用率是 85。

也可以直接检查条件:

1
p cpu_usage >= 80

预期输出:

1
True

因为 85 大于等于 80,所以条件成立,程序应该进入 if 分支。

这里的 p 只负责显示表达式的计算结果,不会自动把结果保存到变量中。

例如:

1
p cpu_usage + 5

输出:

1
90

这并不会改变 cpu_usage 的值。

如果我们想知道一个程序为什么进入了某个分支,可以先查看条件相关的变量,再检查条件表达式的计算结果。

使用 pp 命令查看复杂数据

在服务器巡检程序中,我们可能会处理多个服务器的信息。这些信息通常使用字典或列表保存。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
servers = {
"web01": {
"ip": "192.168.1.10",
"cpu": 85,
"status": "warning"
},
"web02": {
"ip": "192.168.1.11",
"cpu": 35,
"status": "normal"
}
}

如果程序当前作用域中存在 servers 变量,可以输入:

1
pp servers

pp 是 pprint 的缩写,会以更便于阅读的方式显示复杂数据结构。

对于简单变量,使用 p 就足够了;如果需要查看嵌套字典、较长的列表或 API 返回的数据,pp 往往更方便。

使用 n 命令逐行执行代码

断点让程序停在目标位置之后,我们还可以通过 n 逐步检查后面的执行过程。

n 是 next 的缩写,表示执行下一行代码,然后再次暂停。

在第 14 行断点处,输入:

1
n

调试器会执行条件判断。由于 result 为 True,接下来会停在 if 分支中的:

1
print("警告:CPU 使用率过高")

此时再次输入:

1
n

程序就会执行这条 print() 语句,输出:

1
警告:CPU 使用率过高

之后,调试器会停在后续待执行的代码位置。

需要注意,n 会实际执行当前代码,而不仅仅是移动到下一行。如果执行的是函数调用,通常会执行完整个函数调用,再停在调用语句之后的位置。

例如:

1
result = calculate_cpu(cpu_usage)

如果使用 n 执行这一行,调试器通常不会进入 calculate_cpu() 函数内部逐行检查,而是运行函数后返回结果。

如果我们希望进入函数内部检查,就需要使用 s。

使用 s 命令进入函数内部

s 是 step 的缩写,表示单步执行。

与 n 不同,如果当前代码调用了另一个函数,s 可以进入函数内部。

例如,重新启动调试器:

1
python -m pdb cpu_check.py

在 (Pdb) 提示符下,先输入:

1
b calculate_cpu

再输入:

1
c

程序会继续运行,并在进入 calculate_cpu() 函数时暂停。

调试器会显示类似下面的信息:

1
2
3
> cpu_check.py(2)calculate_cpu()
-> print("正在检查 CPU 使用率")
(Pdb)

此时可以检查函数参数:

1
p cpu_usage

输出:

1
85

然后输入:

1
n

程序执行打印语句,并停在后续代码位置。

继续输入:

1
n

程序执行:

1
result = cpu_usage >= 80

这时可以查看:

1
p result

输出:

1
True

这说明条件判断的结果已经保存在变量中。

n 和 s 的区别可以总结为:

  • n:执行下一行,通常不进入函数调用内部。
  • s:执行下一步,遇到函数调用时可以进入函数内部。

如果怀疑某个函数内部的逻辑存在问题,可以使用 s 进入函数逐步检查。

使用 w 命令查看函数调用关系

w 是 where 的缩写,用于显示当前的调用栈。

调用栈可以理解为:程序为了到达当前执行位置,经过了哪些尚未返回的函数调用。

例如,我们的程序调用关系是:

1
2
3
模块顶层代码
└── check_server()
└── calculate_cpu()

当程序在 calculate_cpu() 内部暂停时,输入:

1
w

调试器会显示当前调用路径,帮助我们了解程序是如何进入这个函数的。

当程序包含很多层函数调用,或者某个函数是通过其他模块间接调用时,调用栈就特别有用。

需要注意,调用栈描述的是当前仍处于调用过程中的函数关系,并不是程序执行过的所有代码记录。

使用 r 命令运行到当前函数返回

r 是 return 的缩写,在 pdb 中表示继续执行,直到当前函数返回。

例如,我们已经进入 calculate_cpu() 函数,并且确认函数参数没有问题。如果不想再逐行检查函数剩余部分,可以输入:

1
r

调试器会继续执行当前函数,直到函数即将返回调用者的位置。

这适合我们只关心函数最终返回结果,而不需要检查每一行代码的情况。

需要注意,r 的目标是当前函数返回,而不是无条件运行到整个程序结束。

使用 h 命令查看帮助

如果忘记了某个调试命令,可以输入:

1
h

调试器会显示可用命令的帮助信息。

如果只想查看某个命令的用法,可以输入:

1
h next

或者:

1
h break

这样就能查看 next 或 break 命令的具体说明。

不必一次记住所有命令。遇到不熟悉的命令时,可以先通过帮助信息了解用法。

使用 q 命令退出调试器

当我们已经完成检查,或者决定不再继续调试时,可以输入:

1
q

q 是 quit 的缩写,会退出当前调试会话并终止被调试的程序。

需要注意,q 和 c 的作用完全不同:

  • c:继续运行程序,直到下一个断点或程序结束。
  • q:退出调试器,终止当前被调试程序。

如果只是暂时不想逐行检查,但仍然希望程序继续运行,应使用 c,而不是 q。

完整命令行调试实战

前面分别介绍了常用命令,现在将它们整合到一个完整的命令行调试流程中。

继续使用前面的 cpu_check.py 文件。为了避免每次都手动执行多条 n 命令,我们先设置断点,再让程序自动运行到指定位置。

启动调试器并设置断点

执行:

1
python -m pdb cpu_check.py

在 (Pdb) 提示符下输入:

1
b 14

这会在第 14 行的条件判断处设置断点。

接着输入:

1
b

确认断点已经设置成功。

继续运行到断点

输入:

1
c

程序开始执行,输出:

1
2
开始检查服务器
正在检查 CPU 使用率

随后在第 14 行暂停:

1
2
3
> cpu_check.py(14)check_server()
-> if result:
(Pdb)

这时说明程序已经执行完函数调用,并准备判断 result 的值。

检查变量和条件表达式

依次输入:

1
p cpu_usage

输出:

1
85

继续输入:

1
p result

输出:

1
True

再输入:

1
p result == True

输出:

1
True

通过这些检查,我们可以确认变量值和条件判断都符合预期。

单步执行并观察输出

输入:

1
n

程序执行条件判断,并停在告警语句附近。

再次输入:

1
n

程序输出:

1
警告:CPU 使用率过高

调试器接着停在后续代码位置。

如果希望查看当前调用栈,可以输入:

1
w

如果想查看附近的源代码,可以输入:

1
l

这两条命令不会像 n 那样推进程序执行位置,而是帮助我们了解当前的执行环境。

继续运行到程序结束

确认当前分支没有问题后,输入:

1
c

由于后面没有其他断点,程序会继续执行,输出:

1
2
服务器检查完成
巡检程序执行结束

调试会话结束后,就完成了这次命令行调试。

整个过程可以概括为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
启动 pdb
|
v
设置断点 b 14
|
v
继续执行 c
|
v
程序在断点处暂停
|
v
检查变量 p
|
v
逐步执行 n 或进入函数 s
|
v
确认执行逻辑
|
v
继续执行 c 或退出 q

这就是一个比较完整的命令行调试流程:先确定关注的位置,再通过断点让程序自动运行到那里,然后检查变量和代码,最后决定继续执行还是退出调试。

动手练习:自己设置和管理断点

现在请尝试独立完成一次调试。

继续使用前面的 cpu_check.py 文件。先确认源代码没有发生变化,再按照下面的步骤操作。

启动调试器并设置两个断点

在终端中执行:

1
python -m pdb cpu_check.py

在调试器中设置两个断点:

1
2
b 4
b 14

第一个断点位于 calculate_cpu() 函数内部,用于检查 CPU 使用率的计算过程;第二个断点位于 check_server() 函数中的条件判断位置。

输入:

1
b

查看断点列表。假设第一个断点编号为 1,第二个断点编号为 2,后续请以实际显示的编号为准。

运行到第一个断点

输入:

1
c

程序会运行到第 4 行:

1
result = cpu_usage >= 80

此时第 4 行尚未执行。

输入:

1
p cpu_usage

预期结果为:

1
85

接着输入:

1
n

调试器执行第 4 行,完成条件判断。

输入:

1
p result

预期结果为:

1
True

这说明 CPU 使用率判断的结果已经保存到局部变量 result 中。

运行到第二个断点

在第一个断点处完成检查后,输入:

1
c

程序会继续执行 calculate_cpu() 的剩余代码,返回判断结果,然后回到 check_server() 函数。

接下来程序会在第 14 行暂停:

1
if result:

注意,此时第 14 行的条件判断还没有执行。

输入:

1
p result

预期结果为:

1
True

这说明 check_server() 接收到的返回结果为 True,程序接下来将进入 if 分支。

禁用第一个断点

假设我们已经检查完第一个断点,暂时不希望程序下次再停在那里,但还想保留它以便以后使用。

输入:

1
disable 1

然后输入:

1
b

查看断点列表。第一个断点应显示为禁用状态,第二个断点仍然处于启用状态。

接着输入:

1
enable 1

重新启用第一个断点,再输入:

1
b

确认第一个断点已经恢复启用状态。

禁用断点不会让程序立即跳转,也不会撤销已经执行的代码。它只是让调试器在后续执行过程中暂时忽略该断点。

删除第一个断点并验证执行流程

为了清楚观察删除断点后的执行过程,重新启动调试器:

1
python -m pdb cpu_check.py

在 (Pdb) 提示符下设置两个断点:

1
2
b 4
b 14

查看断点列表:

1
b

假设第一个断点编号为 1,第二个断点编号为 2。后续操作请以实际显示的编号为准。

输入:

1
c

程序会运行到第 4 行:

1
result = cpu_usage >= 80

此时第 4 行还没有执行。

现在删除第一个断点:

1
clear 1

如果调试器要求确认,按照提示输入 y 并按回车。

删除完成后,程序仍然暂停在第 4 行。删除断点不会自动执行当前语句,也不会自动跳转到下一个断点。

输入:

1
c

程序从当前暂停位置继续执行,完成 CPU 使用率判断,并返回 True。随后程序回到 check_server() 函数,最终在第 14 行的第二个断点处暂停。

调试器会显示类似下面的信息:

1
2
3
> cpu_check.py(14)check_server()
-> if result:
(Pdb)

此时输入:

1
p result

预期输出:

1
True

这说明程序已经完成 CPU 使用率判断,并将返回值传递给了 check_server() 函数。

接下来输入:

1
c

程序执行条件判断,并输出:

1
2
3
警告:CPU 使用率过高
服务器检查完成
巡检程序执行结束

至此,删除第一个断点后的执行流程验证完成。

删除断点只会移除暂停条件,不会改变当前执行位置。程序从哪里继续执行,取决于当前暂停的位置和下一条调试命令。

继续练习删除断点后的行为

如果想进一步验证删除断点的效果,可以再次启动调试器:

1
python -m pdb cpu_check.py

重新设置两个断点:

1
2
b 4
b 14

输入:

1
c

程序会在第 4 行暂停。此时输入:

1
clear 1

按照提示确认删除。

接着输入:

1
c

程序会继续执行,不会再因为第 4 行的断点而暂停。如果第 14 行断点仍然启用,程序依然会在第 14 行暂停。

这说明:删除某个断点,只会让程序不再因为该断点而暂停,不会影响其他断点,也不会改变程序本身的业务逻辑。

常用调试命令速查表

命令作用
l查看附近的源代码
n执行下一行,通常不进入函数内部
s单步执行,可以进入函数内部
p expression查看变量或表达式的值
pp expression更整齐地显示复杂数据
b 14在指定行设置断点
b function_name在指定函数入口设置断点
b查看断点列表
disable 1禁用编号为 1 的断点
enable 1启用编号为 1 的断点
clear 1删除编号为 1 的断点
c继续执行程序
r运行到当前函数返回
w查看当前调用栈
h查看调试器帮助
q退出调试器并终止被调试程序

写在最后

本篇文章学习了 Python 标准库 pdb 中常用的调试命令,并通过完整的命令行调试过程,将启动调试器、设置断点、检查变量、逐步执行和继续运行等操作串联起来。

对于刚开始学习 Python 的同学,建议优先掌握 l、b、c、n、s、p 和 q。这些命令已经能够帮助我们完成许多常见的调试任务。

下一篇将进一步学习如何设置和管理断点,包括如何在不同位置暂停程序,以及如何根据实际调试需求控制断点。