Pdb小教程

本文用于介绍pdb的使用方式和使用场景.

PS1: 下文每个小标题里的小括号是指令的别名,中括号里的数字表示该指令的使用频率或重要程度(1表示经常使用,2表示有时使用,3表示偶尔使用,4表示一般不用).

PS2: 本文教程基于Python3


如何进入Pdb调试模式

修改代码

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(next)[1]

n 执行当前行

s(step)[1]

s 执行当前行,如果是函数的话会进入函数

c(continue)[1]

c 继续运行

r(return)[1]

r 继续运行到当前函数返回

q(quit)[1]

q 退出程序


调试最重要的是找到出错的地方,而n,s,c,r,q就是在找出出错的代码时最常用的指令.

ns,要注意的是,ns都是单步进行,执行当前行,但如果当前行是一个函数(或者说不是一个简单的语句),使用n会简单地执行完这个函数,然后跳到下一行,而s则是会进入这个函数的代码块位置.如果我们觉得这个函数没什么问题,我们使用n即可.如果我们觉得函数里有问题,则使用s进入函数.

c是继续运行,其实就是不一行一行调试了,直接正常执行之后的代码,直到遇到断点/程序报错/程序结束为止.一般是配合断点b在程序开始时使用.

r是运行到当前函数返回,与c类似,它也是不再一行一行调试了,但它会在当前的函数(函数栈)返回时停下来,而c不会.一般我们s进一个函数,没发现什么特别的时候,就可以用r让它运行到这个函数结束.

q是退出程序,一般python程序强制退出是ctrl+c.但如果pdb在后台监控的情况下,程序会中断运行并进入调试模式,这个时候要使用q才能退出程序.在debug找到了错误之外,我们也会使用q来退出程序,修改代码重新运行.


辅助控制流

unt(until)[2]

unt 233 执行到233行才停下来

j(jump)[2]

j 233 跳转到第233行

run(restart)[4]

run 重启程序


untr类似,r是运行到函数返回时重新停下来,而unt则是运行到中间某行时停下来.通常一个函数很长或者中间有循环的情况下,我们想尽快运行到后面的内容,用n会很慢,断点+c的做法要设断点很麻烦,这个时候就可以用unt.unt有点像一次性的断点.

junt有点像,但unt是运行到某行停下来,而j则是直接跳到那行.有时候我们想跳过前面没有错误的过程,而直接测试后面的过程,又不想修改代码的时候,就经常使用j来跳转.

run用来重新启动程序,很少使用.


断点

b(break)[1]

b 查看所有断点
b 233 在233行设置断点
b fx 在函数fx入口设置断点
b a/b.py:233 在a/b.py文件里的233行设置断点
b 233,a==3 设置条件,只有满足条件,才停下来

condition[2]

condition 4 a==3 对第4个断点设置条件
condition 4 取消条件

cl(clear)[1]

cl 3 清除第3个断点
cl 3 5 7 清除多个断点
cl 清除所有断点(需要再按y确认)

enable/disable[3]

disable 3 禁用第3个断点
disable 3 5 6 禁用多个断点
enable 4 重新激活第4个断点
enable 4 6 8 重新激活多个断点

ignore[4]

ignore 3 4 忽略第3个断点4次

tbreak[4]

tbreak 233 一次性断点


b,断点是调试里最重要的内容之一.如果说基础控制流是为了在某个局部区域搜索错误代码行,那么断点就是快速进入这个局部区域的工具.断点b通常会跟c配合用,先设置好断点,然后直接c,等到程序运行到断点,它就会停下来.

Python里设置断点的参数有几种格式(行号/函数名/文件+行号),后面还有一些指令(tbreak)的参数也是这几种格式.另外,每个断点都会有一个id,使用b可以查看所有断点的id(和它的各种属性),后面一些指令(condition/enable/disable/ignore)针对断点的操作都是使用这个id索引.

condition,有时候设置了断点并不一定需要某个断点停下来,而是它符合某种条件(比如出错)才停下来,这个时候可以使用condition给某个断点设置条件,满足这个条件才会停下来.

cl是删除断点.我们调试过程中,不想用某一个断点了的时候,可以使用cl删掉它.

enabledisable比较少用,当我们调试过程中想临时屏蔽一个断点,而不是直接删除它,就可以使用disable.enable则是想重新激活它的时候使用.

ignore则更少用.在我们想调试循环中间某一次的时候,可以使用ignore.不过更常用的是condition,只是你条件不好写,但清楚你要在第几次停下来的时候,才会用ignore.

tbreak很少用,就是一次性的断点,断点只想停一次的话,就用tbreak.


查看与执行

p/pp[1]

p cnt 查看cnt这个变量的值
pp cnt 以更好看的打印方式(pretty print)查看cnt这个变量的值

![1]

!x=233 执行x=233这个命令

l(list)/ll(longlist)[1]

l 查看当前行为中心的上下代码块,继续按会查看后面的代码
l . 始终查看当前行为中心的上下代码块
l 14 查看14为中心的上下代码块
l 14,20 查看14到20行之间的代码块
ll 查看当前函数的代码块

a(args)[3]

a 查看当前所有局部变量的值

whatis[4]

whatis cnt 查看cnt的类型


断点用于帮助快速进入到关键代码段,控制流用于寻找出错的代码,而查看和执行命令则是最终实现寻找动作的指令.

p命令可以当前查看某个变量的值是什么,!命令可以执行一个Python语句. 但是实际上我们很少用这两个命令,这是因为pdb如果无法识别一个输入命令的时候,它会把它当成是Python代码执行(相当于变成了Python交互模式),所以我们可以直接输入变量的名字来查看变量的值,可以直接输入Python语句去执行语句.

注意,如果你的变量的名字是类似c,r,p这些,pdb则会理解成pdb指令,想要查看它们的值还是要乖乖使用p命令,执行命令时也是同理,类似a=3的指令是不行的.

l是查看代码的指令,有时候需要查看现在位置的代码的时候,可以使用l和它的各种变种.

a是用来查看当前所有的局部变量(或者说当前名字空间里的所有变量)的值,比较少使用.

whatis很少使用,它其实是个语法糖,可以使用type(xxx)实现.


函数栈控制流

w(where,bt)[3]

w 查看当前函数栈

u(up)[3]

u 跳到上一层函数栈
u 3 往上跳3层函数栈

d(down)[3]

d 跳到下一层函数栈
d 3 往下跳3层函数栈


当我们需要在函数栈上进行跳转和查看的话,需要用到上述三个指令.

有时候我们进入了很深层的函数里,突然想要查看外层函数的某一个变量的时候,我们就可以先使用u跳回上层函数查看,再使用d命令回到最里层的函数.而w则是在我们跳转到头晕的时候,查看当前我们在哪一个函数栈里.


其他指令

h(help)[1]

h 查看有什么命令
h whatis 查看whatis这个命令的帮助

alias/unalias[3]

alias 查看所有命令别名和对应命令
alias ls l .l .这个命令命名为ls(以后可以直接使用ls这个命令执行l .)
unalias ls 取消ls这个命名

interact[3]

进入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实际是指一个文件.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)

你可能感兴趣的:(python)