
作者 | Rocky0429
来源 | Python空间大家好,我是 Rocky0429,一个只会写 bug 的
蒟蒻...不管用什么语言在编写程序的时候,总会出现形形色色的 bug,由于程序员经常被玩坏,各种属于程序员的“俚语”也被大众熟知,出现了 bug 估计连我隔壁老王的
三姨妈的四表舅的远房表姐的邻居的同学的妹夫的外婆的还在喝奶的侄女都知道要 debug,问题是 debug 说的容易,如何“de”,估计很多人还是一脑门儿

在遥远的 n 年前,那个时候我还是个憨批,阿不,是个蒟蒻。那个时候我在 写 C 语言(我的第一门编程语言)的时候,出现了 bug 用 printf 查错,可能写的程序又多又烂,printf 被我用的炉火纯青,出神入化,debug 的速度比我洗脑壳的时间还要快上一分。

Python 的手段比当年 C 的手段还多了不少,除了“打印”(print)以外,还可以配合 assert、log 来分析错误原因,再加上单元测试效率还是四颗星的。但是这种方式,更多的适合“自娱自乐”的情况。啥是自娱自乐,就是这段代码是你写的,你知道代码什么意思,而且最好这个代码比较...短。

后来入了 ACM 的坑,有了能让我抱大腿的队友,成了一位光荣的划水选手。我不能只看自己的代码了,我的 printf 如陷入了泥沼中,寸步难行,在“大腿”鄙视的眼神中,被安利了「单步调试」,它的丝滑让我欲罢不能,只能用这两个字形容:

因为对它的迷恋,在学 Python 的第一时间,我就查了如何调试 Python 代码,我发现 Python 对于 debug 的支持还是很给劲的,常用的有两种:
pdb 调试和断点 + 单步调试。
0x00 pdb 调试
pdb 是 Python 自带的库,为 Python 提供了一种交互式的源码调试功能,包含当前调试器应有的功能,包括设置断点、单步调试、查看源码等。其实如果你之前学过 C/C++ 的话,你可能知道 gdb 这个命令行调试工具,如果你之前用过 gdb,那么恭喜你你可以直接用 pdb 了,因为两个用法是一样的。
其实还有一个开源的 Python 调试器 -- ipdb,它和 pdb 的接口是一样的,但是 ipdb 相比于 pdb 多了语法高亮,tab 自动补全等友好功能,在易用性方面做了很大的改进,这个感觉就和 Python 和 IPython 一样。
关于 pdb 调试和 ipdb 调试的用法,我在以前的文章中也写过,感兴趣的可以看一下:
Python 调试器,一个优秀开发人员的必备技能包。
0x01 断点 + 单步调试
这个更多的是在图形界面下的调试,很多 IDE 都支持 Python,调试功能都很完善,这里我主要介绍在 Pycharm 下的调试,毕竟 Pycharm 可以说是当前最好用的 Python IDE。
断点
在断点这个地方,主要分为两步:「找断点」和「打断点」。
找断点,就是你想调试的代码块的第一行代码即可,也就是一个断点接足够了,找到以后就可以打断点了。可能有同学就有这么一个问题:
我都不知道是哪部分出了问题,我哪知道我要调试哪段代码?

好像有点道理,不要慌,问题不大。首先你在关键的代码位置上 print,然后通过分析 print 的值来缩小范围,当然这个过程你要稳住,可能需要重复多次,一般这样就可以将范围缩小到一个比较完整的功能代码块中,然后就打断点好了。
所以知道为啥叫“打”断点了吧,谁让它那么难找...

下面来说打断点的方法,就是单击目标代码的行号右边空白处,然后出现一个红红的圈,就证明打断点成功了,请看下图:

单步调试
断点打完了,那么就该「单步调试」了。
调试的方法非常简单,就是在当前的 py 文件内部点鼠标右键,单击“Debug xxx”(xxx 是 py 文件名):

此时 Pycharm 会调出一个控制台,这个控制台大概分为显示内容的区域和工具区域:

其中工具区域有两个面板:Debugger 和 Console。对于 Debugger 面板,它的内容是在内容区域显示,显示的为程序执行过程中的变量及细节;Console 面板则是输出数据显示的位置。
对于我们的代码,如果到调试状态,该行代码就处于一个冻结的状态,在点击"按步操作"之后,才会一行一行代码执行。
主要用到 Step Over 这个按钮:

单击 Step Over 这个按钮之后,代码会跳到下一行,这时代表着第一行代码已经执行完毕,此时在 Debugger 面板显示如下:

在上图中我们看到了在程序运行状态下变量中的值,这样一来,程序的运行过程对于我们来说就变的很透明了,我们就可以看到在运行过程中这个值是不是我们期望的值,如果不是,那这就是出 Bug 的原因。
然后我们继续单击 Step Over 按钮,一直到最后一步:

然后你会在 Debugger 面板内看到所有的变量及其值和类型,然后我们再来单击 Step Over 按钮,让我们来看一下发生了什么:

天呐个天,Debugger 面板中什么都没了...

不要慌,这都是有缘由的。
还记得在那很久很久以前,我给大家讲的变量么...

忘记没事,听我再慢慢道来...
这要从盘古开天辟地,阿不,女娲补天说起...
变量是啥子呢?
我们知道变量是存储数据的。
但是变量它住在哪呢?
住在内存里,当然这个住只能是暂住,毕竟内存就这么大点儿,都赖着不走那内存很快就满了。
它临时存储再内存当中,啥叫临时存储,就是到点儿了就赶你走,变量的"到点儿"就是程序执行完的时候,那个时候内存中的变量就没了,所以我们就什么也看不到了。
Debugger 面板完事儿了,下面就该看 Console 面板了:

当我们切换到 Console 面板的时候,我们看到了两个 print 输出的结果(两个结果是在一步步 Step Over 的过程中,每执行完一个 print 之后产生一个对应的结果)。
使用断点 + 单步调试的方法可以很完美的展示程序"自上而下"执行这一特征,同时可以查看程序的执行细节、变量类型和数据输出这些内容,可以更快速的找出 bug,理解程序运行的过程,省时省力又省心。
文章中我用了很简单的一段代码来演示在 Pycharm 中如何打断点,如何单步调试,其实对于复杂的程序,也是由一个个简单的代码块堆积起来,掌握了方法,多写 bug,阿不,多用调试方法,相信随着这个过程的进行,你一定能成为一个 debug 小能手。
-END-