Python调试工具pdb使用详解
1 前言
最近在实习公司刚拿到关于自动化部署服务器的Python代码,mentor让我先自己学习一下这个代码。这个代码主要是使用Python来使一些服务器节点能够自动化搭建产品的运行环境,所以代码需要放在Linux操作系统上运行。不过并没有一个带桌面的服务器使用,对于只会使用Pycharm来调试代码的我,突然觉得无从下手。后来想到c/c++有gdb调试工具,所以我也到网上搜索了一下,发现Python也有相似的调试工具—pdb.。因此通过自己的一些学习对pdb的相关使用方法进行总结。
2 参考文档
pdb-Python调试器-Python3.7.4rc1文档
17.8.pdb交互式调试器|《Python3标准库实例教程》|Python技术论坛
3 pdb简介
pdb为Python程序定义了一个交互式源码调试工具。pdb支持在源码的行级别设置(条件)断点和单步执行以及进入函数调试、查看当前代码、查看栈片段、动态改变变量的值等。同时还支持事后调试,并可以在程序控制下调用。
4 pdb使用命令行调试
pdb可以作为脚本调试其他脚本,进入调试模式指令:
python3 –m pdb myscript.py
命令行调试模式下,如果正在调试的程序异常退出,pdb将自动进入事后调试。在事后调试或者程序正常退出后,pdb将会重启程序,并会保留pdb状态(例如断点)。
4.1 举例代码
from Test2 import *
def test1_function1(list1, list2):
content1 = "Test1 Function1 List1"
list1.append(content1)
content2 = "Test1 Function1 List2"
list2.append(content2)
test2_function1(list1, list2)
finish_content = "Test1 Function1 Finished"
list1.append(finish_content)
list2.append(finish_content)
def test1_function2(list1, list2):
test1_function1(list1, list2)
content1 = "Test1 Function2 List1"
list1.append(content1)
content2 = "Test1 Function2 List2"
list2.append(content2)
finish_content = "Test1 Function2 Finished"
list1.append(finish_content)
list2.append(finish_content)
if __name__ == '__main__':
list1 = ["List1 Main"]
list2 = ["List2 Main"]
test1_function2(list1, list2)
print(list1)
print(list2)
def test2_function1(list1, list2):
content1 = "Test2 Function1 List1"
list1.append(content1)
content2 = "Test2 Function1 List2"
list2.append(content2)
loop_test(list1, list2)
finish_content = "Test2 Function1 Finished"
list1.append(finish_content)
list2.append(finish_content)
def loop_test(list1 ,list2):
for i in range(5):
list1.append(i)
list2.append(i)
4.2 调试器命令
本文使用 Test1.py 和 Test2.py 讲解pdb相关命令
以下列出调试器相关命令,大多数命令可以缩写为一个或者两个字母,如h(elp)表示h或者help都可以使用命令的功能。命令的参数必须使用空格或制表符分隔,[ ]方括号中的参数表示可选参数,使用可选参数时不需要键入方括号。当输入空行时会执行上一条命令。
4.2.1 进入pdb调试模式
(1) 进入pdb命令行调试模式 : python –m pdb Test1.py
从命令行运行调试器时载入代码并停止在程序的第一个声明
4.2.2 帮助指令
(2) h(elp) [command]
没有参数时,打印可用命令列表,使用命令作为参数时,则打印该命令的相关帮助。
4.2.3 控制程序执行
(3) s(tep)
执行当前行,在当前函数的下一行或者调用函数内的第1条语句停止。
(4) n(ext)
执行当前行,并在当前函数的下一行停止,与step类似但不进入正在执行的语句调用的函数,next指令会跳过函数执行的细节。
(5) unt(il) [lineno]
如果没有参数,则继续执行代码,直到代码行数大于当前行数,所以没有参数时,与next指令功能相同。使用参数,指定行号,则执行程序直到达到大于或等于该数字的行数的代码处。该指令可用于跳至循环的结束。如果行号大于当前函数行号的范围,则会在当前函数返回时停止。
(6) r(eturn)
程序继续执行,直到当前函数返回时或即将执行return语句时停止。便于在函数返回之前查看返回值。
(7) c(ont(inue))
程序继续执行,直到遇到断点时停止。
(8) j(ump) lineno
设置将要执行的下一行。jump命令在运行时改变程序的流程,而不修改代码。 它可以向前跳过以避免运行某些代码,也可以向后跳转以再次运行它。向前跳转会将执行点移动到当前位置之后,而不会执行其间的任何语句。跳转还可以将程序执行移动到已执行的语句,以再次运行它。
跳入和退出某些流控制语句是危险的或未定义的,因此调试器不允许这样做。不能跳转到for循环的中间或跳出finally子句。
4.2.4 设置断点
(9) b(reak) [([filename:]lineno | function) [, condition]]
使用lineno参数时,表示在当前文件指定行号处设置断点。使用function参数时可在该函数的第一条可执行语句处设置断点。行号还可以带有文件名和冒号前缀,以指定另一个文件的断点,注意文件应在sys.path路径中。每创建一个断点,则为断点分配一个所有其他断点命令可引用的数字bpnumber。
如果使用第二个参数condition,则它应该是一个表达式,在断点被接受之前该表达式的值应为true。
如果没有参数,则列出所有断点的相关信息。
(10) tbreak[([filename:]lineno | function) [, condition]]
临时断点在程序执行第一次命中时会自动清除。 使用临时断点可以很容易地快速到达程序流程中的特定位置,就像使用常规断点一样,但是由于它会立即清除,因此如果程序的该部分重复运行,则不会干扰后续过程。参数设置与break相同。
(11) cl(ear) [filename:lineno | bpnumber[bpnumber...]]
使用filename:lineno参数时,清除此行的断点,使用以空格分隔的断点编号为参数时,清除指定编号的断点,没有参数时清除所有断点(需要确认)。
(12) disable [bpnumber [bpnumber...]]
禁用以空格分隔的断点编号,禁用断点将会使断点失效无法停止程序,但与清除断点不同,它会保留断点并可以重新启用。
(13) enable [bpnumber [bpnumber...]]
启用指定的断点。
(14) ignore bpnumber [count]
使用循环或使用对同一函数的大量递归调用的程序时,通常可以通过在执行中跳过来更容易地进行调试,而不是观察每个调用或断点。ignore命令告诉调试器在不停止的情况下跳过断点。 每次处理遇到断点时,它都会减少忽略计数器。 当计数器为零时,重新激活断点。当count参数省略时,默认为0。
(15) condition bpnumber [condition]
为断点设置一个新条件,这个表达式必须在断点被接受之前计算为true。如果condition不存在,则删除任何现有条件,即将有条件的断点设置为无条件断点。condition参数必须是使用在定义断点的堆栈帧中可见的值的表达式。与break的condition参数用法相同。
4.2.5 查看函数堆栈
(16) w(here)
可用来确切地找出正在执行的行以及程序所在的调用堆栈的位置,最近的帧位于底部。箭头表示当前帧。
(17) d(own)
在堆栈中向下移动帧,移动至较新的帧。
(18) u(p)
向堆栈中较旧的帧移动。每次向上或者向下移动堆栈时,调试器都会以where相同的格式打印当前位置。
4.2.6 查看堆栈上的变量
(19) a(rgs)
打印当前函数的参数。
(20) p expression
计算作为参数的表达式并打印结果。
(21) pp expression
与p命令一致,区别是使用pprint模块进行漂亮的打印
(22) whatis expression
打印表达式的类型
(23) display [expression]
每次执行在当前帧中停止时,显示表达式的值(如果已更改)。如果没有表达式,列出当前帧的所有显示表达式。
(24) undisplay [expression]
清除在当前帧中活动的显示表达式。如果没有表达式,清除当前帧的所有活动的显示表达式。
4.2.7 查看源码
(25) l(ist) [first[, last]]
在当前位置周围添加更多上下文,默认设置是列出当前行周围的 11 行(前五行和后五行)。 使用单个数字参数时列出指定行周围的 11 行而不是当前行。如果 list 使用两个参数时,它会显示指定范围的代码。
(26) ll | longlist
命令打印当前函数或帧的源,而不必事先确定行号。 函数源码较多的,它可能打印比 list 的默认值更多的代码。
(27) source expression
获取并打印类,函数,或模块的完整源代码
4.2.8 交互式操作
(28) commands [bpnumber]
使用commands可以在遇到特定断点时执行一系列解释器命令,包括Python语句。执行commands,调试器提示符变为(com)。以此输入一个命令,并以end结束以保存脚本并返回主调试器提示符。如果没有bpnumber参数,cammands指向最后一个断点。可通过continue、step、next、return、jump、quit以及它们的缩写终止命令列表(已输入的代码无效)。
(29) interact
启动一个交互式解释器,其中已填充当前帧中的全局变量和局部变量。可以从交互式解释器更改 list 等可变对象。 不可变对象不能改变,并且名称不能绑定到新值。使用文件结束序列 Ctrl + z ,并回车退出交互式提示并返回调试器。
(30) !statement
pdb调试器允许在当前函数中执行Python语句,如果该Python语句类似与调试器指令,可以在前加上感叹号,表示将表达式传递给Python解释器。
4.2.9 自定义调试器别名
(31) alias [name [command]]
使用 alias 定义快捷方式,以避免重复键入复杂命令。别名扩展应用于每个命令的第一个单词。 别名的主体可以包含在调试器提示符下输入合法的任何命令,包括其他调试器命令和纯 Python 表达式。 别名定义允许递归,因此一个别名甚至可以调用另一个别名。
不带任何参数运行 alias 会显示已定义别名的列表。
单个参数时打印指定的别名。
使用 %n 引用别名的参数,其中 n 被替换为表示参数位置的数字,从 1 开始。 要使用所有参数,请使用%*。
(32) unalias name
清除一个别名的定义。
4.2.10 重启pdb调试
(33) run [args ...]
当调试器到达程序结束时,它会自动将其重新启动,但也可以显式重新启动,而无需离开调试器并丢失当前断点或其他设置。.run 重新启动程序。 传递给 run 的参数用 shlex 解析并传递给程序,好像它们是命令行参数一样,因此可以使用不同的设置重新启动程序。
(34) restart [args ...]
与run指令用法一致,restart是run的别名。
4.2.11 退出pdb调试
(35) q(uit)
退出调试器。正在执行的程序被中止。
5 使用.pdbrc文件
调试程序包含了大量重复操作:运行代码、观察输出、调整代码或输入,然后再次运行。 pdb 试图减少所需的重复次数来控制调试体验,让你专注于代码而不是调试器。 为了减少向调试器发出相同命令的次数, pdb 可以在启动时读取解释的文本文件中保存的配置。
进入pdb调试模式时,首先读取文件 ~/.pdbrc (环境变量“HOME”路径下的.pdbrc文件),所有项目进入pdb调试模式时都会读取的配置文件。 然后读取 ./.pdbrc(当前目录下的.pdbrc文件),特定项目才会读取的配置文件。
-
windows下新建.pdbrc文件
在当前目录下进入cmd,输入echo >.pdbrc
,即可创建.pdbrc文件。
-
打开.pdbrc文件,输入想要执行的指令,并保存。
n n n b 33 b 10 c c a p list1 ll
-
进入pdb调试模式
python3 -m pdb Test1.py
6 pdb调试工具的不足
pdb 调试有个明显的缺陷就是对于多线程,远程调试等支持得不够好,同时没有较为直观的界面显示,不太适合大型的 python 项目。而在较大的 python 项目中,这些调试需求比较常见,因此需要使用更为高级的调试工具比如pycharm。