写过C语言的同学们想必都很怀念(读者:¿)gdb调试器,使用gdb可以随意在程序运行过程中暂停流程、查看变量。
很多时候,我们单纯分析代码流程和日志信息无法定位的问题,都得靠调试器来帮忙;可以说有了调试器,程序员才是代码世界完整的上帝。
Python/ target=_blank class=infotextkey>Python当然也不示弱,同样存在这样的巴别塔可以让人升天
——不过阿酱必须承认的是,现代IDE集成的图形化调试功能已经很好使了,一般情况下使用命令行工具的场景并不多。
但是也确实存在无法使用图形化IDE的情况,因此对pdb工具略作了解还是很有必要的。毕竟谁也不知道可能被扔给一个什么样的环境啊哈哈
作为解释型语言,Python调试工具的使用跟gdb毕竟还是有区别的。
比如Python的调试就不需要什么符号表之类的东西,说到底,最终Python虚拟机执行的逻辑也是自带符号的。
也正是由于Python的这种特殊性,所有pdb其实有两种不太一样的使用方式,即侵入式和非侵入式。
其实按字面意思就很容易理解在两种方式的使用。类比一下脑机接口,也分为侵入式和非侵入式。侵入式就表示要将电极、芯片植入大脑皮层,“侵入”人体;而非侵入式则是在头骨外收集脑电波进行分析。
同样地,侵入式pdb调用就是将调用pdb的代码直接写入Python脚本当中;而非侵入式则是从命令行调用pdb,执行相应被调试脚本。
侵入式pdb
使用方式如下代码所示,在代码中途插入一行调用:
import pdb; # pdb.set_trace() a = "just"b = "do" pdb.set_trace() c = ['p', 'y', 't', 'h', 'o', 'n']print(a)
运行脚本,会进入这样一个交互式界面:
D:D: 00-Githubpython-examplesxuanyuanyulong2020-11-04-python-pdb>python test_pdb_intrusive.py> d: 00-githubpython-examplesxuanyuanyulong2020-11-04-python-pdbtest_pdb_intrusive.py(21)<module>()-> c = ['p', 'y', 't', 'h', 'o', 'n'](Pdb)00-GitHubpython-examplesxuanyuanyulong020-11-04-python-pdb>python test_pdb_intrusive.py> d:D: 00-GitHubpython-examplesxuanyuanyulong2020-11-04-python-pdb>python test_pdb_intrusive.py> d: 00-githubpython-examplesxuanyuanyulong2020-11-04-python-pdbtest_pdb_intrusive.py(21)<module>()-> c = ['p', 'y', 't', 'h', 'o', 'n'](Pdb)00-githubpython-examplesxuanyuanyulong020-11-04-python-pdbtest_pdb_intrusive.py(21)<module>()-> c = ['p', 'y', 't', 'h', 'o', 'n'](Pdb)
到这里已经启动了pdb,并且打印内容中-> c = ['p', 'y', 't', 'h', 'o', 'n']行首的箭头,表示当前程序执行流到了这一行代码,如果继续执行,将首先执行该行。
非侵入式pdb
非侵入式要xue微简单一些,最大的好处是不需要改动代码。
我们在控制台执行以下命令:
D:D: 00-GitHubpython-examplesxuanyuanyulong2020-11-04-python-pdb>python -m pdb test_pdb_intrusive.py> d: 00-githubpython-examplesxuanyuanyulong2020-11-04-python-pdbtest_pdb_intrusive.py(1)<module>()-> import pdb; # pdb.set_trace()(Pdb)00-GitHubpython-examplesxuanyuanyulong020-11-04-python-pdb>python -m pdb test_pdb_intrusive.py> d:D: 00-GitHubpython-examplesxuanyuanyulong2020-11-04-python-pdb>python -m pdb test_pdb_intrusive.py> d: 00-githubpython-examplesxuanyuanyulong2020-11-04-python-pdbtest_pdb_intrusive.py(1)<module>()-> import pdb; # pdb.set_trace()(Pdb)00-githubpython-examplesxuanyuanyulong020-11-04-python-pdbtest_pdb_intrusive.py(1)<module>()-> import pdb; # pdb.set_trace()(Pdb)
可以看到,通过这种方式进入调试,程序执行流停在了程序开头。
通过分析进入调试时代码执行流的位置,我们可以发现,实际上侵入式的插入pdb.set_trace()调用,等价于我们从命令行启动pdb,然后在这个调用的下一行打了一个断点,然后直接运行程序。
gdb中有一些常用的简单命令,本节阿酱带大家熟悉一下,后续会做更深入的讨论。
h(elp)
在pdb界面下输入h或help命令,即可列出pdb中支持的各种命令:
(Pdb) h Documented commands (type help <topic>):========================================EOF c d h list q rv undisplaya cl debug help ll quit s untalias clear disable ignore longlist r source untilargs commands display interact n restart step upb condition down j next return tbreak wbreak cont enable jump p retval u whatisbt continue exit l pp run unalias where Miscellaneous help topics:==========================exec pdb
在pdb后带一个命令作为参数,还可进一步看到相应的使用说明:
(Pdb) h hh(elp) Without argument, print the list of avAIlable commands. With a command name as argument, print help about that command. "help pdb" shows the full pdb documentation. "help exec" gives help on the ! command.
相信我,help其实才是pdb里面最重要的命令。别的什么都可以记不住,但是help一定要记住。在以结果为导向的职场生活中也是一样,遇到问题要及时求助哟~
l(ist)
打印当前文件的源代码。不带参数的话,默认打印当前行前后共计11行代码。继续执行该命令的话,则会继续往后打印最多11行代码,直到遇上文件结束符EOF。
用.作为参数则限定要强一点,只会打印当前行前后11行代码。
(Pdb) l 1 -> import pdb; # pdb.set_trace() 2 3 4 def addStr(a, b): 5 return a + b 6 8 return ''.join(l) 9 10 def getSlogan(a, b, c): 11 result = addStr(a, b) + mergeChar(c)
当指定两个参数时,则打印这个区间内的代码:
(Pdb) l 3, 7 3 4 def addStr(a, b): 5 return a + b 6 7 -> def mergeChar(l: list):
而当第二个参数b比第一个参数a小的时候,则表示“从第a行开始,继续往后打印b行”,也就是总共打印(1+b)行:
(Pdb) l 7, 3 7 -> def mergeChar(l: list): 8 return ''.join(l) 9 10 def getSlogan(a, b, c):
p/pp
打印某个对象的值。区别在于pp调用的是pprint函数,打印更加美观。
(Pdb) p a'just'(Pdb) p addStr<function addStr at 0x000002087B0F9C80>