本文用于介绍pdb的使用方式和使用场景.
PS1: 下文每个小标题里的小括号是指令的别名,中括号里的数字表示该指令的使用频率或重要程度(1表示经常使用,2表示有时使用,3表示偶尔使用,4表示一般不用).
PS2: 本文教程基于Python3
import pdb
pdb.set_trace()
python -m pdb test.py
想要进入python的pdb调试模式主要有两种方法.一是修改代码,在需要停下来的地方加入pdb.set_trace()
语句,当程序运行到这句话的时候就会自动停下来进入调试模式.二是在启动脚本的时候使用python -m pdb test.py
的方式,这样的话,程序会在test.py的第一句话就停下来.
第一种方式,一般用在确认了问题出在哪里之后,相当于直接在那里设了一个断点.第二种方式则是从头开始调试,但第二种方式可以实现第一种方式(断点+c,设置断点在那个地方,然后让程序继续执行),并且下文会介绍第二种方式更多有用的手法.
要注意的是,程序运行的时候,pdb有可能在后台监控.如果pdb在后台监控的时候程序发生错误,则程序会停下来进入pdb模式,此时可以执行查看命令找出问题,但不能让程序继续执行了.如果pdb不在后台监控,则程序发生错误会直接结束python进程.
使用第一种方式的话,pdb会在执行到pdb.set_trace()
之后才会在后台监控,而第二种方式,一开始pdb就会在后台监控.
对于不清楚程序是否出错,使用第二种方式会更好,因为当程序抛出异常,程序就会停下来进入调试模式,你可以通过查看变量找出问题所在,而不需要重新运行一次程序.第二种方式的不好之处在于,它在程序结束的时候会进入调试模式,准备重新运行程序,并且不会释放内存或显存.
n
执行当前行
s
执行当前行,如果是函数的话会进入函数
c
继续运行
r
继续运行到当前函数返回
q
退出程序
调试最重要的是找到出错的地方,而n,s,c,r,q
就是在找出出错的代码时最常用的指令.
n
和s
,要注意的是,n
和s
都是单步进行,执行当前行,但如果当前行是一个函数(或者说不是一个简单的语句),使用n
会简单地执行完这个函数,然后跳到下一行,而s
则是会进入这个函数的代码块位置.如果我们觉得这个函数没什么问题,我们使用n
即可.如果我们觉得函数里有问题,则使用s
进入函数.
c
是继续运行,其实就是不一行一行调试了,直接正常执行之后的代码,直到遇到断点/程序报错/程序结束为止.一般是配合断点b
在程序开始时使用.
r
是运行到当前函数返回,与c类似,它也是不再一行一行调试了,但它会在当前的函数(函数栈)返回时停下来,而c
不会.一般我们s
进一个函数,没发现什么特别的时候,就可以用r
让它运行到这个函数结束.
q
是退出程序,一般python程序强制退出是ctrl+c.但如果pdb在后台监控的情况下,程序会中断运行并进入调试模式,这个时候要使用q
才能退出程序.在debug找到了错误之外,我们也会使用q
来退出程序,修改代码重新运行.
unt 233
执行到233行才停下来
j 233
跳转到第233行
run
重启程序
unt
和r
类似,r
是运行到函数返回时重新停下来,而unt
则是运行到中间某行时停下来.通常一个函数很长或者中间有循环的情况下,我们想尽快运行到后面的内容,用n
会很慢,断点+c
的做法要设断点很麻烦,这个时候就可以用unt
.unt
有点像一次性的断点.
j
和unt
有点像,但unt
是运行到某行停下来,而j
则是直接跳到那行.有时候我们想跳过前面没有错误的过程,而直接测试后面的过程,又不想修改代码的时候,就经常使用j
来跳转.
run
用来重新启动程序,很少使用.
b
查看所有断点
b 233
在233行设置断点
b fx
在函数fx入口设置断点
b a/b.py:233
在a/b.py文件里的233行设置断点
b 233,a==3
设置条件,只有满足条件,才停下来
condition 4 a==3
对第4个断点设置条件
condition 4
取消条件
cl 3
清除第3个断点
cl 3 5 7
清除多个断点
cl
清除所有断点(需要再按y确认)
disable 3
禁用第3个断点
disable 3 5 6
禁用多个断点
enable 4
重新激活第4个断点
enable 4 6 8
重新激活多个断点
ignore 3 4
忽略第3个断点4次
tbreak 233
一次性断点
b
,断点是调试里最重要的内容之一.如果说基础控制流是为了在某个局部区域搜索错误代码行,那么断点就是快速进入这个局部区域的工具.断点b
通常会跟c
配合用,先设置好断点,然后直接c
,等到程序运行到断点,它就会停下来.
Python里设置断点的参数有几种格式(行号/函数名/文件+行号),后面还有一些指令(tbreak
)的参数也是这几种格式.另外,每个断点都会有一个id,使用b
可以查看所有断点的id(和它的各种属性),后面一些指令(condition/enable/disable/ignore
)针对断点的操作都是使用这个id索引.
condition
,有时候设置了断点并不一定需要某个断点停下来,而是它符合某种条件(比如出错)才停下来,这个时候可以使用condition
给某个断点设置条件,满足这个条件才会停下来.
cl
是删除断点.我们调试过程中,不想用某一个断点了的时候,可以使用cl
删掉它.
enable
和disable
比较少用,当我们调试过程中想临时屏蔽一个断点,而不是直接删除它,就可以使用disable
.enable
则是想重新激活它的时候使用.
ignore
则更少用.在我们想调试循环中间某一次的时候,可以使用ignore
.不过更常用的是condition
,只是你条件不好写,但清楚你要在第几次停下来的时候,才会用ignore
.
tbreak
很少用,就是一次性的断点,断点只想停一次的话,就用tbreak
.
p cnt
查看cnt这个变量的值
pp cnt
以更好看的打印方式(pretty print)查看cnt这个变量的值
!x=233
执行x=233这个命令
l
查看当前行为中心的上下代码块,继续按会查看后面的代码
l .
始终查看当前行为中心的上下代码块
l 14
查看14为中心的上下代码块
l 14,20
查看14到20行之间的代码块
ll
查看当前函数的代码块
a
查看当前所有局部变量的值
whatis cnt
查看cnt的类型
断点用于帮助快速进入到关键代码段,控制流用于寻找出错的代码,而查看和执行命令则是最终实现寻找动作的指令.
p
命令可以当前查看某个变量的值是什么,!
命令可以执行一个Python语句. 但是实际上我们很少用这两个命令,这是因为pdb如果无法识别一个输入命令的时候,它会把它当成是Python代码执行(相当于变成了Python交互模式),所以我们可以直接输入变量的名字来查看变量的值,可以直接输入Python语句去执行语句.
注意,如果你的变量的名字是类似c,r,p这些,pdb则会理解成pdb指令,想要查看它们的值还是要乖乖使用p
命令,执行命令时也是同理,类似a=3
的指令是不行的.
l
是查看代码的指令,有时候需要查看现在位置的代码的时候,可以使用l
和它的各种变种.
a
是用来查看当前所有的局部变量(或者说当前名字空间里的所有变量)的值,比较少使用.
whatis
很少使用,它其实是个语法糖,可以使用type(xxx)
实现.
w
查看当前函数栈
u
跳到上一层函数栈
u 3
往上跳3层函数栈
d
跳到下一层函数栈
d 3
往下跳3层函数栈
当我们需要在函数栈上进行跳转和查看的话,需要用到上述三个指令.
有时候我们进入了很深层的函数里,突然想要查看外层函数的某一个变量的时候,我们就可以先使用u
跳回上层函数查看,再使用d
命令回到最里层的函数.而w
则是在我们跳转到头晕的时候,查看当前我们在哪一个函数栈里.
h
查看有什么命令
h whatis
查看whatis这个命令的帮助
alias
查看所有命令别名和对应命令
alias ls l .
给l .
这个命令命名为ls
(以后可以直接使用ls
这个命令执行l .
)
unalias ls
取消ls这个命名
进入Python交互模式
alias
用于给一些很长的命令/Python语句起一个简短的别名,一般是配合下面介绍的pdbrc一起使用.unalias
则是删除这个别名.
interact
用于进入Python的交互模式,进入交互模式的好处主要体现在名字空间上.假如要对两个长度为n的数组a,b执行逐元素加法,Python语句为k = [a[i]+b[i] for i in range(n)]
.在pdb调试模式执行这句话是不行的,而在Python交互模式是可以的,这主要是因为调试模式在执行迭代生成式的时候,会新建一个生成式里的名字空间,此时a和b就不在这个空间里,因此报错.
另外,进入了Python交互模式的话,使用exit()
等方法可以退出回到调试模式.
pdbrc实际是指一个文件.pdbrc
,这个文件里存放着一些pdb指令.当使用第二种方式python -m pdb test.py
运行程序时,如果当前目录存在.pdbrc
文件,则pdb会执行这些命令,而不是傻傻地停在test.py
文件的第一行,这也是第二种方式的终极用法.
有时候我们可能会在修改代码和运行调试之间循环工作,直到找到所有bug为止.如果bug一般在某个区域的话,我们可以事先在.pdbrc
文件里写好一些指令(b,j,c
等等),方便我们快速进入到核心区域进行调试.
另外,我们也可以使用alias
事先给某些指令起好别名,方便我们在调试时使用某些冗长的指令.
下面是一个深度学习中调试测评程序的.pdbrc
文件的例子:
#断点
b 57
b lib/evalor.py:48
b lib/metric.py:109
c
#跳过训练
j 59
c
#读入事先准备好的特征文件,跳过提取特征
import numpy as np
features = np.load('feat.npy')
j 52
c
#停在metric.py的109行,打印一下重要变量
print(features.shape)
print(topk)
print(self.setting)