pdb基本使用方法

概要

本文只要讲述一下pdb(The Python Debugger)的基本使用方法,pdb提供了强大、灵活的Python调试功能。之前我一直用的Pycharm或者vscode带的调试器,但在我使用过程中发现它们有时候会出现某些意想不到问题后(本来想调试自己代码的问题,调着调着就变成找调试器的bug了>_<)。

Pycharm中的一个问题

注:这里例子给的是Pycharm 2019.2.1这个版本的例子,不知道后面有没有更新解决

python debug

如上面的工程,简单定义了两个文件,一个名为threading.py,里面啥也没有,一个就是test.py,代码就是图中简单的几行。设置断点开始调试,结果蹭蹭地弹出异常,如下图所示

python pydevd.py exception

呃呃,这个问题对于当时的我困扰许久,还以为Pycharm哪里没设置对,或者下载重装都没解决,后来大概看一下pydevd.py就发现问题。不过如果import的是内置的threading就不会出错,不过这种覆盖内置模块名字还是少干,容易出错,因为有时候你用的第三方库就依赖某个内置模块,而被你覆盖后就容易报错了。

其他

上面的问题跑到vscode中就不存在,可以正常调试。另外在vscodePycharm中调试某些程序的时候(这里还没总结出简单的例子,不过本文不是这个重点所以大概提提),发现调试器一直无法获取程序上下文,简单说就是卡住了,看不到有啥变量以及变量的值是啥,这样的使得程序无法继续调试。最后,最最重要的是,当你只能在服务器或者只能远程连接都只能用终端操作的时候,这个时候就没辙了,乖乖的用起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启动程序,就会进入调试状态。

常用调试命令

注:()表示可以省去,如helph是一样的;[]表示选项,可以选择性输入。

命令 参数 说明
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了。

最后,要了解更多的命令细节,在实践中操作很容易掌握,凡事还是多动手才能熟练起来。

0%