非程序员的 Python 2.6 调试教程
- "在我们开始编程的时候,我们惊讶地发现让程序正常运行并不像我们想象的那样容易。调试不得不被发现。我记得我意识到我以后生命中的很大一部分将会花在寻找自己程序中的错误的那一刻。" — 莫里斯·威尔克斯 发现调试, 1949
到目前为止,如果你一直在使用你编写的程序,你可能已经发现有时程序会做一些你不想它做的事情。这是相当常见的。调试是弄清楚计算机在做什么,然后让它做你想让它做的事情的过程。这可能很棘手。我曾经花了将近一周的时间追踪和修复一个错误,这个错误是由某人在应该使用 y
的地方输入了 x
引起的。
本章将比之前的章节更抽象。
第一步(这听起来很明显)是弄清楚如果程序正常运行它应该在做什么。想出一些测试用例并看看会发生什么。例如,假设我有一个程序用来计算矩形的周长(所有边的长度之和)。我有以下测试用例
高度 | 宽度 | 周长 |
---|---|---|
3 | 4 | 14 |
2 | 3 | 10 |
4 | 4 | 16 |
2 | 2 | 8 |
5 | 1 | 12 |
我现在对所有测试用例运行我的程序,看看程序是否按预期执行。如果它没有按预期执行,那么我需要找出计算机在做什么。
更常见的是,一些测试用例会通过,而一些则不会通过。如果是这样,你应该尝试弄清楚通过的测试用例有什么共同点。例如,这里是一个周长程序的输出(你将在稍后看到代码)
Height: 3 Width: 4 perimeter = 15
Height: 2 Width: 3 perimeter = 11
Height: 4 Width: 4 perimeter = 16
Height: 2 Width: 2 perimeter = 8
Height: 5 Width: 1 perimeter = 8
注意,它对前两个输入没有生效,对接下来的两个输入生效,对最后一个输入没有生效。尝试找出有效输入的共同点。一旦你对问题有一些了解,找到原因就更容易了。对于你自己的程序,如果你需要,你应该尝试更多测试用例。
接下来要做的是查看源代码。编程过程中最重要的一个事情是阅读源代码。执行此操作的主要方法是代码演练。
代码演练从第一行开始,一直向下执行,直到程序结束。While
循环和if
语句意味着某些行可能永远不会被执行,而某些行会被执行很多次。在每一行,你需要弄清楚 Python 做了什么。
让我们从简单的周长程序开始。不要输入它,你将阅读它,而不是运行它。源代码是
height = input("Height: ")
width = input("Width: ")
print "perimeter =", width + height + width + width
- 问题:Python 运行的第一行是什么?
- 答案:第一行总是先被执行。在本例中,它是:
height = input("Height: ")
- 那行代码做了什么?
- 打印
Height:
,等待用户输入一个数字,并将该数字放入变量 height 中。 - 下一行是什么?
- 一般来说,它是下一行,也就是:
width = input("Width: ")
- 那行代码做了什么?
- 打印
Width:
,等待用户输入一个数字,并将用户输入的内容放入变量 width 中。 - 下一行是什么?
- 当下一行没有比当前行缩进更多或更少时,它就是紧随其后的行,所以它是:
print "perimeter = ", width + height + width + width
(它也可能在当前行执行一个函数,但这将在下一章中讨论。)那行代码做了什么? - 首先它打印
perimeter =
,然后它打印width + height + width + width
。 width + height + width + width
能正确地计算周长吗?- 让我们看看,矩形的周长是底边(宽度)加上左边(高度)加上顶边(宽度)加上右边(嗯?)。最后一项应该是右边的长度,也就是高度。
- 你明白为什么有时周长被“正确”地计算出来吗?
- 当宽度和高度相等时,它被正确地计算出来了。
我们将要进行代码演练的下一个程序是应该在屏幕上打印 5 个点的程序。但是,这是该程序的输出
. . . .
这是程序
number = 5
while number > 1:
print ".",
number = number - 1
print
这个程序的演练将更复杂,因为它现在有缩进部分(或控制结构)。让我们开始吧。
- 要运行的第一行是什么?
- 文件的第一行:
number = 5
- 它做了什么?
- 将数字 5 放入变量 number 中。
- 下一行是什么?
- 下一行是:
while number > 1:
- 它做了什么?
- 嗯,
while
语句通常会查看它们的表达式,如果它是真值,它们就会执行接下来的缩进代码块,否则它们就会跳过接下来的缩进代码块。 - 所以它现在做了什么?
- 如果
number > 1
是真值,那么接下来的两行将被执行。 - 那么
number > 1
吗? - 最后放入
number
的值是5
,而5 > 1
,所以是真值。 - 那么下一行是什么?
- 由于
while
是真值,所以下一行是:print ".",
- 那行代码做了什么?
- 打印一个点,并且由于语句以 ',' 结尾,所以下一个 print 语句将不会出现在不同的屏幕行上。
- 下一行是什么?
number = number - 1
,因为它是下一行,并且没有缩进变化。- 它做了什么?
- 它计算
number - 1
,也就是number
的当前值(或 5),从中减去 1,并将其设为number
的新值。所以基本上它将number
的值从 5 更改为 4。 - 下一行是什么?
- 嗯,缩进级别减少了,所以我们必须查看它是哪种类型的控制结构。它是一个
while
循环,所以我们必须回到while
子句,也就是while number > 1:
- 它做了什么?
- 它查看
number
的值,也就是 4,并将其与 1 进行比较,由于4 > 1
,所以 while 循环继续执行。 - 下一行是什么?
- 由于 while 循环是真值,所以下一行是:
print ".",
- 它做了什么?
- 它在该行上打印第二个点。
- 下一行是什么?
- 没有缩进变化,所以它是:
number = number - 1
- 它做了什么?
- 它取
number
的当前值(4),从中减去 1,得到 3,然后最终将 3 设为number
的新值。 - 下一行是什么?
- 由于 while 循环结束导致缩进变化,所以下一行是:
while number > 1:
- 它做了什么?
- 它将
number
的当前值(3)与 1 进行比较。3 > 1
,所以 while 循环继续执行。 - 下一行是什么?
- 由于 while 循环条件是真值,所以下一行是:
print ".",
- 它做了什么?
- 它在该行上打印第三个点。
- 下一行是什么?
- 它是:
number = number - 1
- 它做了什么?
- 它取
number
的当前值(3),从中减去 1,并将 2 设为number
的新值。 - 下一行是什么?
- 回到 while 循环的开头:
while number > 1:
- 它做了什么?
- 它将
number
的当前值(2)与 1 进行比较。由于2 > 1
,所以 while 循环继续执行。 - 下一行是什么?
- 由于 while 循环正在继续:
print ".",
- 它做了什么?
- 它发现了生命、宇宙和一切的意义。我在开玩笑。(我必须确保你清醒着。)这行代码在屏幕上打印第四个点。
- 下一行是什么?
- 它是:
number = number - 1
- 它做了什么?
- 取
number
的当前值(2),从中减去 1,并将 1 设为number
的新值。 - 下一行是什么?
- 回到 while 循环:
while number > 1:
- 这行代码做了什么?
- 它将
number
的当前值(1)与 1 进行比较。由于1 > 1
是假值(1 不大于 1),所以 while 循环退出。 - 下一行是什么?
- 由于 while 循环条件是假值,所以下一行是 while 循环退出后的行,也就是:
print
- 那行代码做了什么?
- 使屏幕进入下一行。
- 为什么程序没有打印 5 个点?
- 循环过早退出了一个点。
- 我们如何解决这个问题?
- 让循环晚退出一个点。
- 我们如何做到这一点?
- 有几种方法。一种方法是将 while 循环更改为:
while number > 0:
另一种方法是将条件更改为:number >= 1
还有其他几种方法。
你需要弄清楚程序正在做什么。你需要弄清楚程序应该做什么。弄清楚两者之间的区别。调试是一项需要练习才能掌握的技能。如果你在一个小时后仍然无法弄清楚,那就休息一下,和别人谈谈这个问题,或者思考一下肚脐里的绒毛。过一会儿再回来,你可能会对这个问题有新的想法。祝你好运。