Python扩展学习系列:pdb调试详解
Python拓展之pdb调试
简介
我现在用的代码编辑器是vscode,再用vscode之后就很少去使用调试了。我觉得可能一开始配置c、c++时有些困难,似乎调试还需要一些配置,还是比较繁琐,成功配置c、c++后,几乎就没再用过调试功能了。
前几天心血来潮,想用vscode调试python代码,本来以为可能也需要配置,后来查阅了一下,原来不需要,直接通过vscode对python代码调试就可以,而且也十分清晰、明显。在这个过程中呢,我又了解到了另一种python代码调试方式———pdb调试。
pdb调试方法主要还是通过终端,用命令行来进行调试。我自己比较喜欢通过终端操作,尤其是在使用vscode之后,终端+命令行操作必不可免。因为我觉得这是一种锻炼,对之后学习Linux操作系统的话,命令行操作可能会有些帮助。所以看到pdb调试之后,我也感兴趣地学了学。虽然它不如vscode自带的调试器直观方便,但对我来说是种对在终端操作的练习。
下面附带个思维导图,是我从xmind上截下来的,不知道是否清晰。
上面看着有点小,不知道发布之后点开图片放大能不能看清楚,有几处略微错误。
一、进入pdb调试方法
1、通过vscode终端进入
终端中输入命令:python -m pdb test.py
其中,test.py是你的文件名
待调试代码我也给出来吧,注意,肯定是有问题的,要不然就不调试了。
下面是一个筛选2-1000以内素数的方法。代码的整体思路是从2-1000遍历,每遇到一个素数就输出,并存放到列表l之中。下面并不是最优的方法,现在也不用看懂,后续我们调试一下就好了。
l=[]
for i in range(2,1001):
if not l:
l.append(i)
print(f'{i}是素数')
else:
for j in l:
if i%j==0:
continue
elif j==l[-1]:
print(f'{i}是素数')
l.append(i)
结合终端代码和源代码,,我们可以看到,终端第三行的l=[]其实就是源代码的第一行,这表示我们成功进入了pdb调试模式。
2、cmd终端进入pdb调试模式方法
为什么又要弄出个普通终端调试呢?因为我觉得,像在蓝桥杯竞赛中,自带的idle也能debug,应该也是通过pdb方式调试的,但是呢,我也不太熟练,到时候如果需要调试的话,我可以直接通过cmd终端进行pdb调试就好了,方法肯定是多样的。
首先打开cmd终端,这个应该不用多说吧。一遍默认目录是C:\Users\,我们需要切换到存放python代码的盘,有下面两种方式进入。
方法1:
F:
cd Python_code\Data_Struct
python -m pdb test.py
方法2:
python -m pdb “F:\Python_code\Data_Struct\test.py”
注意:方法1中每个换行都是一个回车,上面两个方法是我自己的存放路径,具体的需要看你们自己的python代码存放到了哪里。
具体的大家可以看看上面的命令行
二、基本操作命令行
1、list
简写方法为l。
默认显示11行代码,可以连续使用,显示更多代码。这11行也是有讲究的。其实不说也没问题,但像这些基本的,我知道的话也会说一下的。
上面第一行的->代表此时程序运行到这里,第六行的B代表我在这里设置了一个断点,知道就行,后续会说的。
然后我在终端中输入了list,就展示前11行的结果,可以看到展示的是->之后的11行。而如果->运行到中间,再list的话,它展示的就并不是->之后的11行了,而是->上下5行,加上->的一行,一共11行。
现在我们让程序运行到断点第六行,然后list之后看看是不是上面说的。
可以看到,就是我们上面说的那种情况。
(1)list.
代表返回上一段展示代代码
(2)list start,end
自定义展示第start行和第end行。
上面代码就是我先通过list展示了前11行,然后又通过list 4,5展示了第4、5行,之后又通过list.返回了之前展示的前11行。
2、longlist
简写方法为ll
列出当前函数所有行,可是在我实际运行的时候是将golbal所有行都列出来了,想具体展示某段函数还不太会操作,测试了一下,需要先运行到函数中,然后使用longlist,可以显示完整函数。
这里涉及到了符合操作,这里先不深入,只给出展示的结果吧。
我在全局使用ll,并没有单独展示函数get_prime(),而是所有都展示了
当我给函数添加断点后,并执行到这里时,再使用ll,就会展示完整函数了(不过我记得跟list一样,也会展示11行,所以想展示所以函数的话,可以用list start,end 方法展示完全)
3、next
简写方法为n
执行下一行语句,注意,函数的定义和函数体都是一条完整语句,所以并不会进入函数之中。直接按回车的话,pdb会重复上一条命令,也就是next,所以不需要一直输入next。这个不再进行展示了。
4、step
简写方法为s
进入函数内部进行调试
5、continue
简写方法为c
直接让程序进入下一个断点或者结束运行(没有添加断点的话)
6、break
简写方法为b
主要用来设置断点和查看断点的,下面分别说明一下。
(1)break+行
在第某行添加断点
(2)break+函数名
在某函数前加断点
(3)多文件操作
如果多文件的话(比如导入了某个库,想在某个库中设置断点)可以break 文件名:行 (eg. break test2.py:4)
这里不过多进行讲解,因为我自己现在是单文件操作,后续多文件的话会进行补充的。
(4)查看断点
使用break可以直接看到所有设置过的断点。
7、clear
简写方法为c
清除断点方法clear+断点序号(每设置一个断点,就会有一个断点序号,从1开始的)
清除所有断点,直接使用clear就行。
注意,清楚断点的话,break设置的断点序号不会重置。
上面我进行了一些break、clear操作,可以大体看看。
8、disable
disable+断点序号,暂时禁用某断点
9、enable
enable+断点序号,启用某断点
10、until
until+行号,直接运行到某行
11、print
简写方法p
查看变量值,可以直接打印列表:print([]),使用语法跟python类似(只能说类似,因为是调试中的,应该不会完全一样)使用p的话不需要加()了
12、pretty-print
简写方法为pp
格式化输出,让变量显示地更精准
13、where
显示当前函数调用堆栈
这一般多文件调试的时候会用到,我这里不深入讲解
14、up/down
与where连用,切换上一层、下一层调用栈
15、help
列出pdb所有命令,help+命令名获取更详细信息
16、exit/quit
退出终端
17、set_trace()
这是pdb中的一个方法,使用的话需要import pdb,然后使用时直接pdb.sett_trace(),这样代码运行到这里后就直接自动进入调试了。对于一个没写完的代码,想调试的话可以直接加上这个set_trace()进行调试,然后查看变量有无问题。
三、调试展示
这个例子不太好,很不直观,不建议大家看,还是自己操作一下好些。后续我自己调试的时候找个set_trace()的例子再进行说明吧。
这里我会进行之前错误代码的调试,过程基本上是常用的,可以作为参考。
源代码:
l=[]
for i in range(2,1001):
if not l:
l.append(i)
print(f'{i}是素数')
else:
for j in l:
if i%j==0:
continue
elif j==l[-1]:
print(f'{i}是素数')
l.append(i)
我打算是先next一步一步地走,期间打印一下各个变量,看看哪里有问题。
如图,i=2时,直接就是素数,不用判断,并且成功添加到列表l中。后面在进行循环的话,就会直接进入else语句了。我们再来分析一下,看看哪里有问题。
接下来,我先看看i=5时,输出和变量有没有问题,断点设置可以为break 4,i==5 注意,这里的4指的是第四行,一定是for循环内部的某行,而不是for循环那行。后面的是终止条件,当i=5时停止运行。
此时我们看到,4不是素数,肯定有问题。再看看变量的值是什么。
(中间输错了一个,不要在意)我上面说过,列表l式用来存储素数的,4不是素数,确还在这里。初步分析else存在问题。接下来打算一步一步使用next,并且一同查看变量变化,看看问题出在了哪里。
在我逐步分析后发现了问题所在。内层循环的逻辑是,l是存放所有素数的列表,逐个判断i是否能被列表中的数整除,如果能整除,说明不是素数,退出,如果逐渐判断到列表中的最后一个数,还没有退出,说明这就是个素数,并且添加到l中。
调试时发现,当i=3时,判断它是素数之后,添加到l之中,本来循环应该结束了,又多进行了一次循环,考虑了一下,其实就是l本身是for循环的循环条件(范围)可是又在循环中对l进行append,实时对循环条件用影响,肯定会有问题的。所以这里append后直接break退出循环才对。
可是不幸的是,还是出问题了。又进行了上面的几步,发现是continue那里出错了,如果取余等于0的话,说明不是素数,应该直接break,不用再判断了。
作者:巷北夜未央