概要
本文只要讲述一下pdb(The Python Debugger)的基本使用方法,pdb提供了强大、灵活的Python调试功能。之前我一直用的Pycharm或者vscode带的调试器,但在我使用过程中发现它们有时候会出现某些意想不到问题后(本来想调试自己代码的问题,调着调着就变成找调试器的bug了>_<)。
Pycharm中的一个问题
注:这里例子给的是Pycharm 2019.2.1这个版本的例子,不知道后面有没有更新解决
如上面的工程,简单定义了两个文件,一个名为threading.py,里面啥也没有,一个就是test.py,代码就是图中简单的几行。设置断点开始调试,结果蹭蹭地弹出异常,如下图所示
呃呃,这个问题对于当时的我困扰许久,还以为Pycharm哪里没设置对,或者下载重装都没解决,后来大概看一下pydevd.py就发现问题。不过如果import的是内置的threading就不会出错,不过这种覆盖内置模块名字还是少干,容易出错,因为有时候你用的第三方库就依赖某个内置模块,而被你覆盖后就容易报错了。
其他
上面的问题跑到vscode中就不存在,可以正常调试。另外在vscode和Pycharm中调试某些程序的时候(这里还没总结出简单的例子,不过本文不是这个重点所以大概提提),发现调试器一直无法获取程序上下文,简单说就是卡住了,看不到有啥变量以及变量的值是啥,这样的使得程序无法继续调试。最后,最最重要的是,当你只能在服务器或者只能远程连接都只能用终端操作的时候,这个时候就没辙了,乖乖的用起pdb来调试了。。
直奔主题——pdb基本使用
说了一堆屁话,该到本文主题了!这里示例是在Python2.7中演示。
要调试的程序
这里是一段有bug的程序,如下所示
import pdb
pdb.set_trace()
def func1(x, y):
a = 0
b = 1
result = 0
for i in range(100):
result += x + y
if result > 100:
return a
else:
return b
def func2(x):
result = 0
result = func1(1, 2)
return result + x
result = 0
result = func2(1)
print result
连接到服务器,如下图所示:
进入调试状态
即将开始调试了,首先是进入调试状态,有两种方法,一种是侵入式的调试方法,也就是和上图一样,import pdb然后在需要停下来的地方添加pdb.set_trace(),然后运行Python程序后就会在pdb.set_trace()后一句停下来。另一种则不用动源码,直接用python -m pdb xxx.py启动程序,就会进入调试状态。
常用调试命令
注:()表示可以省去,如help和h是一样的;[]表示选项,可以选择性输入。
| 命令 | 参数 | 说明 |
|---|---|---|
| h(elp) | [command] | 获取命令帮助,没有参数的话则会输出全部调试命令 |
| w(here) | - | 输出当前所在栈帧,>表示当前所在的栈帧,->表示在每个栈帧中执行到的语句 |
| d(own) | - | 移到下一个栈帧,即当前上下文为所到的栈帧 |
| u(p) | - | 移到上一个栈帧,即当前上下文为所到的栈帧 |
| b(reak) | [[filename:]lineno | function[, condition]] | 参数可以是`文件名:行号 |
| tbreak | 同b(reak) | 设置临时断点,参数同b(reak),不同的是该断点进入一次后就被删除了 |
| cl(ear) | [filename:lineno | bpnumber [bpnumber …]] |
| disable | [bpnumber [bpnumber …]] | 禁止指定的多个断点,但不删除,需要的时候可以让其生效 |
| enable | [bpnumber [bpnumber …]] | 让指定的几个断点重新生效 |
| ignore | bpnumber [count] | 设置指定断点失效的次数,即在经过该断断点第count+1次的时候才会进入断点 |
| condition | bpnumber [condition] | 和设置断点的时候一样,对某个断点设置生效的条件 |
| commands | [bpnumber] | 可以在进入断点后执行一些命令集 |
| j(ump) | lineno | 跳转到指定行,指定的行必须在 |
| l(ist) | [first[, last]] | 列出从first到last之间的源代码,省略则列出当前执行点周围11行代码 |
| a(rgs) | - | 打印当前执行函数的参数 |
| p | expression | 执行一个python表达式 |
| [!] | statement | 执行一行python语句 |
| run | [args …] | 重启程序 |
| q(uit) | - | 退出调试器 |
几个命令说明
在上面常用的命令中,其中b(reak),c(ontinue),s(tep),n(ext),r(eturn),l(ist),w(here)这几个命令是更加常用的。
具体用法如上面表中所说那样,主要是要区分以下c(ontinue),s(tep),n(ext)的区别
c(ontinue)表示继续执行,知道遇到下一个断点,如果没有了断点则自动重新启动程序;s(tep)表示单步执行,一步一步走,遇到函数则进入函数继续一步步执行;n(ext)表示在当前栈帧中一步步执行,遇到函数不会进去,而是当成一句直接执行再到下一句。r(eturn)表示返回,直接跳到栈帧的最后一句(不是位置上的最后,而是执行中可达的最后),相当于如果是在函数中则直接调到函数返回的地方。
另外就是b(reak)加上条件,或者用condition给断点加个条件,在调试中有时候也很有用,比如说上面的代码在循环中,循环99次出现bug,要看看循环到100次的时候哪里出bug。在用IDE调试的时候我没找到什么好的方法,只能在源码中添加个if语句再循环99次的时候进入,然后就在if里面打断点。但是用pdb就可以给断点加个条件,当满足条件的时候才进入断点,上面的例子就可以设置断点为:
(Pdb) b 10,i==99
这样的话就可以在进入断点前就循环了99次了,很是方便。当然也可以给已经打了的断点添加条件,利用的就是condition命令,比如说打在第10行的断点的断点号是1,那么就可以:
(Pdb) condition 1 i==99
这样同样能达到上面的效果。
然后就是清除某个断点的条件了,具体的就是再执行以下condition命令,只是没写条件就ok了。
最后,要了解更多的命令细节,在实践中操作很容易掌握,凡事还是多动手才能熟练起来。