如何优雅地使用 Spyder 的调试器
这是一个面向初学者的文章.很多朋友在初学编程的时候很难找出错误,或者对程序运行流程不太清楚.这个时候调试器就要上场了~

前言

这是一个面向初学者的文章. 很多朋友在初学编程的时候很难找出错误,或者对程序运行流程不太清楚. 这个时候调试器就要上场了~ 所以在这里我要以Spyder为例,介绍调试器的用法.

为什么

我们为什么要学会调试

学会调试,对于我们理解程序的运行流程,以及找bug很有帮助. 想一下,如果我们能够在程序运行的时候,实时查看变量是怎么变化,岂不美哉?

为嘛要用Spyder

首先,这是一个面向Python初学者的文章. 考虑到本篇文章的读者可能都是从Anaconda开始的,所以我在此以Spyder为例讲解调试器的使用. 其他IDE的调试功能其实大同小异.


Spyder初体验

熟悉Spyder的界面

我们先来熟悉一下Spyder的界面.

  • 最上面有一排按钮,我们俗称工具栏.
  • 在左侧,我们可以写代码. 按一下工具栏上绿色的开始按钮,即可开始运行程序.
  • 右侧分为上下两栏.
    • 上栏中,有三个选项卡.分别是Variable explorer(这个很重要),File explorer,还有Help(后面两个基本没啥鸟用)
    • 下栏叫做IPython console,我们俗称为控制台. 一般来说,运行代码时候的输出,会在控制台里面看到. 当然,我们可以在没有运行代码的时候,在In [n]: (n是几不用关心)后面打入命令与控制台交互.

用Spyder运行示例代码

现在我们在左侧代码区域,粘贴下面的代码.

a = 0
for i in range(10):
	i += 1
	a += i
print(a)

然后按一下绿色的开始按钮,我们可以在控制台中看到输出. 输出:

55

给初学者的注释: i += 1 等同于 i = i + 1.

调试目标

我们想知道每一次for循环进行的时候,a和 i的值是怎么变化的.

朴素的 print 调试法

这个方法自古以来就有了. (胡说,古代哪有Python) 在没有调试器的漫漫长夜中,这种直接输出到屏幕上的调试方法最为实用.

既然我们想要看a和i在每一次for循环中的值是多少,那么我们直接print出来就好了! 所以我们把代码修改成:

a = 0
for i in range(10):
	i += 1
	print("此时i的值为:",i) # 在i变化后立马print,看看它的值是多少
	a += i
	print("此时a的值为:",a) # 在a变化后立马print,看看它的值是多少
print(a)

运行结果是这样的:

此时i的值为: 1
此时a的值为: 1

中间省略无数个此时

此时i的值为: 10
此时a的值为: 55
55

这样我们就能看出来每次for循环的i和a的变化情况了.

但是这样太麻烦了,而且满屏幕的i的值,a的值,看得人眼晕. 而且当变量很多的时候,我们要加很多print. 最后到了提交之前,还要记得把自己打的这些print给删除掉.

那么,有没有更直观,更优雅的方法呢?

隆重登场的调试器法

我们如果要运行程序,只需要点击工具栏中那个绿色的开始按钮就可以了.

但是这只是单纯的运行. 它会像IDLE一样,正常运行程序,没有调试没有逗留.

第一招式 - 逐行调试

进入调试模式

  1. 想要开始调试,我们需要按下图中所示的1号按钮(Debug File).

  2. 然后我们再点击一下下图中红色箭头所指的Variable explorer.

    • 红色箭头: 一定要记住,我们一定要切换到下图中红色箭头所指的Variable explorer,这样我们才可以看到变量此时的值. (不过因为我们还没有执行第一行代码,所以这里是空的.)
    • 蓝色箭头: 它所指的地方,上面有个---->的符号,并且后面有行号1.这代表当前调试器将要(还未)运行第一行代码. (划重点,不要搞错,「将要」)

开始逐行调试

现在,按下图中所示的2号按钮**(以下称为Run Current Line)**,开始在调试器里面运行下一行代码.

我们可以看到,Variable explorer里面多出了变量a. 其中Name代表这个变量的名称,第二栏Type是这个变量的类型.而最后一栏Value是该变量的.(Size不用管,暂时用不上.) 并且控制台中的---->符号指向了第二行. 说明第一行执行完毕,再次点击将执行第二行.

举个例子: 以图中的变量a为例,这个变量的名称是a,它的类型是int,值为0. (正好也和我们预期的情况相同–我们执行了一句a=0)


我们接着点刚才的Run Current Line.可以看到,Variable explorer中又多出了第二行变量i.并且此时控制台中的---->指向了第三行. 这说明此时,i的值为0,a的值也为0.


我们点击Run Current Line(这是第三次).发现i的值由0变为1. 再点击一次Run Current Line(这是第四次),a的值也变成了1. 注意一下,经过四次逐行执行,控制台输出如下:

ipdb> None
> c:\users\rise\desktop\temp.py(2)<module>()
      1 a = 0
----> 2 for i in range(10):
      3         i += 1
      4         a += i
      5 print(a)

我们可以看到,---->符号指向第二行(for所在的那行). 这说明此时第一次for循环执行完毕.由于还有后面的循环,所以这里程序要跳回for循环的起始位置,继续进行剩下几次循环.


我们通过逐行调试,可以很直观清楚地跟踪程序的运行过程. 如果我们想中途停止此次调试,只需要点击一下6号按钮,即Stop Debugging.

这就是逐行调试了.


小总结

来总结一下:

  1. 要开始调试,需要点击Debug File. 点击之后,会进入调试模式.
  2. 一定记得切换到Variable explorer,这样才能查看变量的变化情况.
  3. 点击Run Current Line,运行第一行代码.
  4. 继续点击Run Current Line这个按钮,会按照程序逻辑运行下一行代码. 直至执行完毕为止.
  5. 通过点击Stop Debugging,我们可以停止调试.

在上述的调试过程中:

  • Variable explorer中将显示当前存在的所有变量. Type表示该变量的类型Value一栏将显示变量的值.(不要看走眼,不是Size一栏)
  • 在控制台中,我们可以通过---->这个符号所指向的行数,看到即将被执行的代码是哪一行.

第二招式 - 断点调试

断点调试顾名思义,是可以在某一行(或多行)设置断点,对该行进行调试. 而逐行调试是对每一行进行调试. 断点调试是更有针对性的调试方法.

如果你能搞清楚逐行调试,那么断点调试也不难.

设置断点

如图. 双击某一行行号前面的空白处,之后在这个地方会出现如图所示的小红点. 这个小红点就是我们所说的断点.

开始调试

如图所示,我们现在在第3行,第4行设置两个断点.

我们按Debug File开始调试代码. 此时可以看到,控制台里面的显示是这样的:

ipdb> > c:\users\rise\desktop\temp.py(3)<module>()
      1 a = 0
      2 for i in range(10):
1---> 3         i += 1
2     4         a += i
      5 print(a)

其中,行号前面的1和2,代表我们设置的两个断点. 所以此时即将运行第一个断点处,即第三行.

继续调试

程序的执行此时暂停.我们要让它继续运行,需要按一下第五个按钮,即Continue executation until next breakpoint: 这个按钮的意思就是,继续执行直到下一个断点. 因为我们第二个断点设置在第四行,所以代码将继续执行,在将要执行第四行的时候会暂停.

断点调试小总结

我来总结一下断点调试的几个步骤.

  1. 双击行号前面的空白处,以设置断点
  2. 点击Debug file开始进入调试
  3. 在调试过程中,通过Variable explorer观察变量的值
  4. 点击Continue executation until next breakpoint执行到下一个断点处.
  5. 通过点击Stop Debugging,我们可以停止调试.

调试器总结

几点需要注意的地方:

  • 每次打开Spyder的时候,右上方区域默认显示的是Help. 所以我们需要手动切换到Variable explorer.
  • 在调试的时候,---->指向的是即将执行的代码,而不是已经执行的代码.
  • 当一次调试完毕后,开始第二次调试的时候,Variable explorer不会自己清空. 这点一定要注意,上一次调试残留的变量的值仍然会留在Variable explorer里面. 所以我们可以按一下下图所示的按钮清空.

写在最后

调试是IDE中很重要的一个功能. 这篇文章虽然字很多,但实际上用起来是很简单的,它并不复杂,而且很适合新手. 如果我们学会了调试器的使用,那么写代码将会事半功倍.

另外,其他的IDE的调试功能也是大同小异.大家可以探索一下!

那么最后呢,就祝各位学有所成~ Cheers!

(未完待续,因为可能会有补充)


上次修改於 2019-10-28