谁说IDLE不能调试?PDB:你小子还是太嫩!

由于竞赛中只能使用Python解释器和Python原生的解释器来写代码,这两者都没有相应的调试功能,但是没想到吧!Python中内置了该功能。

笔者使用的python版本:3.8.10

PDB中的命令不止这些, 笔者这里只列出了一些笔者在使用过程中常用的命令、

初出茅庐

对于pdb的使用,我们首先要知道如何将其召唤出来。

pdb 的使用大部分是在cmd命令提示符)中

在这里我们先创建一个python文件,并写入以下代码

import pdb

pdb.set_trace()
print("hello world")

这时候我们要开启一个cmd的窗口并进入到你的代码所在的文件夹中,执行以下的代码

python -m yourcode_file.py

接下来会出现一个这样的对话效果

【这是你的python文件的路径】>python -m test_codes.py
> 【这是路径】/test_codes.py(259)<module>()
-> print("hello world")
(Pdb)

出现这样的语句就说明你已经成功叫出了pdb调试交互窗口

难度提升

学会了如何调出pdb窗口,接下来我们就要开始学习其中的命令了。

上节中我们知道了如何将pdb窗口中调出来,和cmd一样都是一个黑框框,肯定是要去输入命令的。

这次,我们先从上节中的import开始说起

set_trace()

上节代码:

import pdb

pdb.set_trace()
print("hello world")

上节中我们调用了Python中的pdb库 ,并使用了其中的set_trace()方法

set_trace()就是添加断点的功能。

从上节中的执行效果来看的话,代码的执行在print语句的时候就停下来了。出现了一个箭头指向set_trace()语句的下一行

-> print("hello world")

set_trace()这个语句添加的断点是给下一行用的。

程序执行的时候就会停在这个断点上,我们想干啥就可以干啥去了。

print()或者p

没错,就是print

显而易见,该语句的作用就是打印目前的变量信息。

废话不多说,我们直接尝试一下

先向python文件中写入

import pdb
c = 1
pdb.set_trace()
a = 1
b = 2
pdb.set_trace()
c = a + b
print(c)

同样,我们执行

python -m your_file.py

进入到pdb界面以后,我们执行

(Pdb) print(c)
1

在这里我们使用另一种的打印命令也行,两者的效果是相同的

(Pdb)p c

如果我们尝试打印a的值的话

(Pdb) print(a)

这时候编译器显示

*** NameError: name ‘a’ is not defined

这是为什么呢?

因为添加的断点位置在赋值语句上,a还没有被赋值(在python中就是a变量还是不存在的)

这时候就要隆重介绍下一个语句:next语句

next或者n

next语句用于在断点的基础上向下执行语句。

我们还是使用上面的代码作为例子使用。

当执行到第一个断点的时候,使用next命令

(Pdb)next
> 【这是路径】\test_codes.py(260)<module>()
-> b = 2
(Pdb)

我们看到断点移动到了下一个语句中,而不是下一个断点中。

这时候我们再尝试打印一下变量a就可以用了。

(Pdb)p a
1

因为使用了next语句以后,python中就定义好了变量a,就可以正常使用了。

与上文中相同,next语句也可以被压缩成n来进行使用。

continue或者c

continue用于跳转到下一个断点处。

是不是听着和next的功能很像?且听详细分解。

名称相同不同
continue都可以将代码直接执行到某一个点执行到下一个断点
next-执行到下一行就截止

我们直接上实际的例子来好好感觉一下。

通过上面的next的例子我们知道,使用了next以后,代码跳转到了下一行,也就是

b = 2

这一行上。

现在使用continue试试。

(Pdb) continue
-> c = a + b

(Pdb) 

从源代码中我们看到,使用continue直接执行到下一个断点处,也就是下一个set_trace()的地方。

list或者l

这个命令用于列出断点前后11行的代码,便于设立新的断点。

首先执行以上的代码,然后直接使用看看效果。

(Pdb) l
1     import pdb
2     pdb.set_trace()
3  -> a = 1
4     b = 2
5     pdb.set_trace()
6     c = a + b
7     print(c)
[EOF]

(Pdb)

将断点左右的代码都显示出来了。

代码的显示是有限制的,超过一定行数的将不会多余显示,显示功能仅针对于断点附近的代码。

where或者w

这段代码用来查看执行到断点的时候,程序中调用的堆栈信息。

说人话就是代码执行到这一步目前需要的函数/方法有哪些。

执行的效果:

(Pdb) where
  c:\users\carro\appdata\local\programs\python\python38\lib\runpy.py(185)_run_module_as_main()
-> mod_name, mod_spec, code = _get_module_details(mod_name, _Error)
  c:\users\carro\appdata\local\programs\python\python38\lib\runpy.py(111)_get_module_details()
-> __import__(pkg_name)
  <frozen importlib._bootstrap>(991)_find_and_load()
  <frozen importlib._bootstrap>(975)_find_and_load_unlocked()
  <frozen importlib._bootstrap>(671)_load_unlocked()
  <frozen importlib._bootstrap_external>(848)exec_module()
  <frozen importlib._bootstrap>(219)_call_with_frames_removed()
> g:\codes\python\test_codes.py(261)<module>()
-> pdb.set_trace()

(Pdb)

up或者u

该命令是continue的反向运用,两者的作用是相反的。

也就是跳到上一个断点处。

但是根据AI的描述是,该命令不存在。

注意:该命令能否使用还是未知。请慎用。

break或者b

该命令是BreakPoint的缩写。也就是——断点。但是与上文中的set_trace()不同的是,该方法设立的是该次运行中临时的断点。程序执行结束就没有了。

使用该命令可以为正在执行的代码加上断点,使用改命令创建的断点会返回一个断点的编号。

也就是为正在调试中的代码。

我们要更改一下上面的代码。

import pdb
pdb.set_trace()
a = 1
b = 2
# pdb.set_trace() # 将这段代码注释掉
c = a + b
print(c)

还是一样,我们在cmd中将代码跑起来。

python -m your_file.py

将现在的代码中加上临时的断点。

(Pdb) break 5
Breakpoint 1 【这是路径】\test_codes.py:5 # 这里有一个编号,也就是Breakpoint 1,这里的1就是断电的编号。

(Pdb) continue
> g:\codes\python\test_codes.py(261)<module>()
-> pdb.set_trace() # 将这段代码注释掉
 
(Pdb) 

我们看到代码就跳到了代码中的第五行,也就是我们刚刚加上断点的地方。

step或者s

step命令用于跳入循环或者函数中。

我们要将以下写入python文件中,并在cmd中进行调用。

a = 1
for i in range(5):
	a += 1

print(a)

省去调用的步骤,以下是结果。

-> a = 1
(Pdb) step
-> a = calc()
(Pdb) step
--Call--
-> def calc():
(Pdb) 

如果我们使用的是next语句的话

-> a = 1
(Pdb) n
-> a = calc()
(Pdb) n
-> print(a)
(Pdb) n
11

==执行完成==

从两者中的对比我们不难看出,step的功能和next的功能是十分相似的,但是step会进入到函数/循环中,然后再接着一步步的执行。

函数名称相同不同
next将代码向下执行一行不进入函数或者循环中,遇到了直接使用其结果。
step进入函数或者循环中,遇到了将进入并卡在其第一行上,相当于在其第一行设了一个断点

return或者r

step 的殿后者。

该命令将直接将程序的执行快进到函数(或循环)执行完毕的那一个语句上,将函数直接执行完毕。

-> a = 1
(Pdb) n
-> a = calc()
(Pdb) step
--Call--
-> def calc():
(Pdb) n
-> a = 1
(Pdb) n
-> for i in range(10):
(Pdb) n
-> a += 1
(Pdb) n
-> for i in range(10):
(Pdb) return
--Return--
> calc()->11
-> return a
(Pdb) n
-> print(a)

这样处理完成以后就不用在函数中一个一个跳了。

jump或者j

将程序的调试调回某一行或者快进到某一行。

我们还是使用上面的代码来进行验证一下。

要将之前加上去的注释去掉。

import pdb
c = 1
pdb.set_trace()
a = 1
b = 2
pdb.set_trace()
c = a + b
print(c)

我们执行代码以后跳到第二个断点处

-> a = 1
(Pdb) continue
-> c = a + b
(Pdb) l
259     c = 1
260     pdb.set_trace()
261     a = 1
262     b = 2
263     pdb.set_trace()
264  -> c = a + b
265     print(c)
[EOF]
(Pdb) jump 261
-> a = 1
(Pdb)

从list中的信息可以看到,我们断点卡在了264行,我们使用断点回调以后,调试语句回到了261行。

jump调试语句在使用的时候可能会导致无法想象的结果,例如前面的代码被重新执行了影响了后面的结果,或者后面的代码被执行以后导致了报错等等问题。

tbreak

临时断点pro版本

使用这种方法生成的断点在第一次被触发的时候就将会被移除(temporary breakpoint,也就是临时断点),下次到达此处的时候就不会再触发断点。

改断点的设置不能在文件中进行, 也是再pdb界面中使用的。

-> a = 1
(Pdb) l
256     # print(data.loc["2024-12-16 17:38:35", "生词"])
257
258     import pdb
259     c = 1
260     pdb.set_trace()
261  -> a = 1
262     b = 2
263     # pdb.set_trace()
264     c = a + b
265     print(c)
[EOF]
(Pdb) tbreak 264
Breakpoint 1 at g:\codes\python\test_codes.py:264
(Pdb) l
[EOF]
(Pdb) continue
Deleted breakpoint 1 at g:\codes\python\test_codes.py:264
> g:\codes\python\test_codes.py(264)<module>()
-> c = a + b
(Pdb) jump 261
> g:\codes\python\test_codes.py(261)<module>()
-> a = 1
(Pdb) l
256     # print(data.loc["2024-12-16 17:38:35", "生词"])
257
258     import pdb
259     c = 1
260     pdb.set_trace()
261  -> a = 1
262     b = 2
263     # pdb.set_trace()
264     c = a + b
265     print(c)
[EOF]
(Pdb) continue
3

我们看到执行了continue语句以后将原来创建的断点删除了。

这就是将该断点使用了以后自动删除的原理了。

condition

添加条件断点。

我们有的时候使用断点的时候,就是用来排查bug的。但是有的时候断点在循环中,循环要几次才能进入问题语句中,这时候我们就要使用条件断点来跳过前面的几次循环,直接干到问题循环中。

还是上面的代码,我们直接进入pdb调试控制台中操作。

-> a = 1
(Pdb) l
256     # print(data.loc["2024-12-16 17:38:35", "生词"])
257
258     import pdb
259     c = 1
260     pdb.set_trace()
261  -> a = 1
262     b = 2
263     # pdb.set_trace()
264     c = a + b
265     print(c)
[EOF]
(Pdb) break 264
Breakpoint 1 at g:\codes\python\test_codes.py:264
(Pdb) break 265
Breakpoint 2 at g:\codes\python\test_codes.py:265
(Pdb) break 261
Breakpoint 3 at g:\codes\python\test_codes.py:261
(Pdb) continue
> g:\codes\python\test_codes.py(264)<module>()
-> c = a + b
(Pdb) condition 2 if a == 2
New condition set for breakpoint 2.
(Pdb) conditoin 1 if b == 2
*** SyntaxError: invalid syntax
(Pdb) condition 1 if b == 2
New condition set for breakpoint 1.
(Pdb) continue
> g:\codes\python\test_codes.py(265)<module>()
-> print(c)
(Pdb) continue
3

从其中使用condition的语句中我们可以看到,使用condition语句要提供对于的断点的编号,还有将要使用的语句。

这里的条件既可以使用if语句,也可以直接使用判断的表达式。(就是把if去掉的那一部分)

在使用的时候要注意断点要先创建好,这样才能知道断点的编号是多少。

disable & enable [bpnumber]

这其实是两个命令,仔细来说,是两个作用相反的命令。

从两者中,我们很容易就知道,disable是用来禁用断点的,这时候断点就不能使用了。

听着是不是和删除很像?

名称两者的区别
删除将断点完全删除,后续无法恢复使用
禁用将断点禁用,后续可以通过enable将断点重新启用。

从上文中我们也知道了enable的用法了。

但是两者对断点的操作都是使用的断点的编号,这意味着你在使用的时候要提前将断点创建好,这样才能-知道自己的断点的编号。

commands [bpnumber]

添加断点中需要执行的操作。

该命令用于设置当到达该断点的时候要执行的一系列操作(执行的是pdb中的命令,也就是上文和下文中介绍的命令)

设置的时候要加上断点的编号

-> a = 1
(Pdb) l
258     import pdb
259     c = 1
260     pdb.set_trace()
261  -> a = 1
262     b = 2
263     # pdb.set_trace()
264     c = a + b
265     print(c)
[EOF]
(Pdb) break 264
Breakpoint 1 at g:\codes\python\test_codes.py:264
(Pdb) commands 1
(com) step
(Pdb) commands 1
(com) p a
(com) end
(Pdb) continue
1
> g:\codes\python\test_codes.py(264)<module>()
-> c = a + b
(Pdb)

断点的command的设置具有覆盖性,如果已经设置过command则会将之前设置的command覆盖掉,替换为最新的命令组

从上文中的演示中我们也可以看到,我们使用的是end表示的命令组的结束。

但是使用step或者其他的**“具有继续执行的功能”**的功能的命令也有类似的功能。命令组结束以后就成功为断点设置了命令组。

whatis

查看变量的数据类型。

在该命令后面加上数据变量可以直接输出该变量的数据类型

-> a = 1
(Pdb) break 264
Breakpoint 1 at g:\codes\python\test_codes.py:264
(Pdb) continue
> g:\codes\python\test_codes.py(264)<module>()
-> c = a + b
(Pdb) whatis a
<class 'int'>
(Pdb) whatis b
<class 'int'>
(Pdb) whatis c
<class 'int'>
(Pdb)

如果变量不存在的话则是输出一个报错的结果。

display

记录变量的改变情况

display可以将变量的变化情况记录下来,分为现在的情况和之前的情况。

-> a = 1
(Pdb) display c
display c: 1
(Pdb) l
257
258     import pdb
259     c = 1
260     pdb.set_trace()
261  -> a = 1
262     b = 2
263     # pdb.set_trace()
264     c = a + b
265     print(c)
[EOF]
(Pdb) break 264
Breakpoint 1 at g:\codes\python\test_codes.py:264
(Pdb) continue
> g:\codes\python\test_codes.py(264)<module>()
-> c = a + b
(Pdb) next
> g:\codes\python\test_codes.py(265)<module>()
-> print(c)
display c: 3  [old: 1]
(Pdb)

从上文的代码中,我们可以看到在264行的时候c变量发生了改变。我们在这里打上断点,跳转下一行以后,display信息就出来了。

其中记录了c变量之前的值和现在的值。

display c: 3  [old: 1]

interact

该功能在python3.2的时候引入

创建一个新的编译器环境。

使用该命令以后,我们会得到一个新的python编译环境,该环境中的任何操作都不会影响到目前正在测试的代码。

-> a = 1
(Pdb) interact
*interactive*
>>> print("hello world")
hello world
>>>

笔者的python版本中,如果直接使用quit()或者exit()则会直接跳回cmd的界面中。

在python3.13中,在界面中使用quit()或者exit()跳出的是该环境,重新回到pdb调试界面中。

报错以后进行调试

使用命令

python -i <程序文件>

以后,我们就可以在报错以以后进入到python控制台中。

在控制台以后,我们首先要使用import语句导入pdb库。

import pdb

导入成功以后,我们再将使用pdb中的一个函数

pdb.pm()

这样我们就进入了pdb调试界面,该怎么操作就怎么操作吧。

提示:进入了界面以后,显示的断点是在程序报错的那个语句上。这里可以访问到报错之前的所有用到的变量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值