使用PDB调试Python程序
学习编程,调试是写过1000行代码后必须掌握的技能,很多同学因为不会调试程序或者没有调试过问题,技能就停止不前了。
学习程序设计时,我提倡在Linux下使用命令行编译,运行,调试程序。学习C语言要掌握GDB,学习Java要掌握JDB,本文是《使用JDB调试Java程序》的Python版本。我在课堂上讲解过使用VSCode调试程序,这是命令行版本。
Python程序中有逻辑错误,就需要使用Pdb来调试了。调试程序在IDE中很方便,但是我们在Linux环境下学习Python,那就需要在命令行下使用Pdb进行调试了。
Pdb不但是个好的调试工具,也是一个好的学习工具,可以让你了解程序的动态执行过程。
学习建议:Linux Bash下打开三个标签页
我们提倡在Linux命令行下学习Python编程。学习时在Ubuntu Bash中通过Ctrl+Alt+T和Ctrl+Shift+T快捷键打开三个标签(tab):一个使用vim编辑代码;一个使用python3编译运行代码;一个使用PDB调试代码。
如下图所示,这样就不用在一个窗口中进行编辑、编译运行和调试的切换了,能提高效率。
如上图, 我们在Linux Bash中输入 vim HelloPDB.py编辑调试示例代码:
def main():
i = 5
j = 6
sum = add(i, j)
print(sum)
sum = 0
for i in range(0,101):
sum = sum + i
print(sum)
def add(augend, addend):
sum = augend +addend
return sum
if __name__ == '__main__':
main()
代码编辑完,我们按 “:w” 进行保存而不是 “:wq” 进行保存退出,这样在编译或调试中遇到问题就可以按Alt+1 进入第一个标签修代码了。
我们按Alt+2 进入第二个标签,使用python3 HelloPDB.py编译运行程序。
我们按Alt+3 进入第三个标签,使用python3 -m pdb HelloPDB.py对程序进行调试。
调试基础
调试程序先要学会设置断点,这样才能让程序停在你感觉有问题的代码处进行排查。学习调试我们要学会设置四种断点:
行断点
函数断点
条件断点
临时断点
我们在PDB中输入 h 或 help 可以查看命令列表:
上图中的b、break、tbreak和condition命令是与设置断点相关的四个常用命令,其中b是break的缩写,可以相互替代。
-> def main():表示Pdb定位了到下一步要执行的代码def main()。
我们通过运行b main命令在main函数开始处设置断点:
如上图,我们输入r命令运行程序,程序会在main函数的第一行i=5处停下。
可以使用step命令运行main函数下一步代码i=5,使用p或pp命令查看变量i的值。
此时,由于还未运行j=6这一行代码,所以无法查看变量j的值。
我们可以使用l或list或ll(l和list打印附近11行源代码,ll打印全部源代码)来查看运行到了源代码的什么位置,下图中的->指示出代码运行到了哪一行:
这里要注意上图是将要运行第4行,但还没有运行。还要注意,这一行是个函数调用。我们继续输入step和list,我们发现代码跳入13行函数体中了:
除了p命令,我们还可以使用a或者args命令查看当前函数的参数列表。
一般说来,调试时遇到函数调用,我们先看调用结果对不对,结果正确,说明函数没有问题,就不用进入函数体了;如果函数调用结果不对,我们才需要进入函数体进行调试。单步跟踪命令next和step在执行一般语句时没有区别,在执行有函数调用的语句时,next会把函数执行完,step会进入函数体。所以在调试时,单步执行我们要优先使用next,这样效率比较高。
现在已经进入函数体了,我们可以运行up把函数执行完,返回到调用处,在这之后执行一般语句,你会发现next和step没有区别。
第8行和第9行是个循环,这两条语句单步执行起来有点费劲。
我们可以通过b 11在第11行设个断点,然后运行c或cont命令就会一下子把循环运行完并停在第十二行。cont是continue的缩写,功能是运行到下一个断点处停止。
我们可以用break命令查看设置的断点的情况。
其实,这里最好设置的是临时断点tbreak——在第一次执行到这个断点之后,就自动删除这个断点,用法和b一样。
还有,如果第8行问题出在i=80处,我们就需要使用条件断点。
条件断点的设置方法是,需要先设置一个断点或临时断点,然后引用该断点的编号breaknumber,设置条件语句。
这里我重新启动了Pdb,我在第9行设置了一个临时断点,它的断点编号breaknumber是3,我希望在i=80的时候停下来,那么根据condition breaknumber condition的输入规则,我们只要输入condition 3 i==80即可,当判断i==80返回True的时候,就会接受断点。
继续运行下去在第9行停了下来,这个时候可以查看i的值为80
这个时候再查看现有断点,可以看到第9行的临时断点已经被删除了。
最后,我们使用q或quit可以退出Pdb。
类的调试——递归的学习
递归算法是一种直接或间接地调用自身的算法。在编写程序时,递归算法对解决一大类问题是十分有效的,它往往使算法的描述简洁而且易于理解。
递归用于解决形式相同,规模不同的问题,能用递归解决的问题都可以转化为循环。递归把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。用递归思想写出的程序往往十分简洁易懂。
递归程序有两个要点:递归公式和结束条件。我们以求整数的阶乘为例:
有了公式,代码就容易写出来了:
class Factorial:
def fact(self, n):
if n==0:
return 1
else:
return n * self.fact(n-1)
def main():
my = Factorial()
print(my.fact(5))
main()
fact(5)的递推过程如下图:
进入调试,我们设置好断点,开始运行:
类的方法调用一次就会形成一个栈帧,我们在Pdb中用where显示栈帧,用up,down可以在栈帧之间跳转。
大家用四次up,四次down 体会一下压栈,出栈,其中>显示了栈帧位置:
感谢谢绎同学帮忙改写Pyhton版本.
参考资料
欢迎关注“rocedu”微信公众号(手机上长按二维码)
做中教,做中学,实践中共同进步!
如果你觉得本文对你有帮助,请点一下左下角的“好文要顶”和“收藏该文”