Python 3 非程序员教程/调试
- "当我们开始编程时,我们惊讶地发现,让程序正确运行并不像我们想象的那么容易。调试不得不被发现。我记得那一刻,我意识到,我余生的很大一部分将花费在寻找我自己的程序中的错误上。" — Maurice Wilkes 发现调试,1949 年
到目前为止,如果你一直在玩弄你的程序,你可能已经发现,有时程序会做一些你不想它做的事情。这很常见。调试就是弄清楚计算机在做什么,然后让它做你想让它做的事情的过程。这可能很棘手。我曾经花费近一周时间追踪和修复一个错误,这个错误是由某人将一个 x
放置在应该放置 y
的位置引起的。
本章将比前面的章节更抽象。
首先要做的事情(这听起来很明显)是弄清楚程序在正确运行的情况下应该做什么。想出一些测试用例,看看会发生什么。例如,假设我有一个程序来计算矩形的周长(所有边的长度之和)。我有以下测试用例
高度 | 宽度 | 周长 |
---|---|---|
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 = int(input("Height: "))
width = int(input("Width: "))
print("perimeter =", width + height + width + width)
- 问题:Python 运行的第一行是什么?
- 答案:第一行总是第一个运行的。在本例中,它是:
height = int(input("Height: "))
- 那行做了什么?
- 打印
Height:
,等待用户输入一个字符串,然后将字符串转换为一个整型变量 height。 - 接下来运行的是哪一行?
- 一般来说,它是下一行,即:
width = int(input("Width: "))
- 那行做了什么?
- 打印
Width:
,等待用户输入一个数字,并将用户输入的数字放到变量 width 中。 - 接下来运行的是哪一行?
- 当下一行与当前行的缩进程度不同时,它就是下一行,所以它是:
print("perimeter = ", width + height + width + width)
(它也可能在当前行运行一个函数,但那是下一章的内容。) - 那行做了什么?
- 首先它打印
perimeter =
,然后它打印变量width
和height
中包含的值的总和,从width + height + width + width
获取。 width + height + width + width
是否正确地计算了周长?- 让我们看看,矩形的周长是底部(宽度)加上左侧(高度)加上顶部(宽度)加上右侧(嗯?)。最后一项应该是右侧的长度,或者说是高度。
- 你明白为什么有时周长会被“正确”地计算出来吗?
- 当宽度和高度相等时,它被正确地计算出来了。
我们将要进行代码走查的下一个程序是一个应该在屏幕上打印 5 个点的程序。但是,这是该程序的输出
. . . .
以下是该程序
number = 5
while number > 1:
print(".",end=" ")
number = number - 1
print()
这个程序的走查将更加复杂,因为它现在有缩进部分(或控制结构)。让我们开始吧。
- 要运行的第一行是什么?
- 文件的第一行:
number = 5
- 它做了什么?
- 将数字 5 放入变量 number 中。
- 下一行是什么?
- 下一行是:
while number > 1:
- 它做了什么?
- 嗯,一般来说,
while
语句会查看它们的表达式,如果表达式为真,它们就会执行下一行缩进的代码块,否则它们就会跳过下一行缩进的代码块。 - 所以它现在做了什么?
- 如果
number > 1
为真,那么接下来的两行将被执行。 - 那么
number > 1
吗? - 最后放入
number
的值是5
,而5 > 1
,所以答案是肯定的。 - 那么下一行是什么?
- 由于
while
为真,所以下一行是:print(".",end=" ")
- 那行做了什么?
- 打印一个点,由于存在额外的参数
end=" "
,所以下一行打印的文本将不会在不同的屏幕行上。 - 下一行是什么?
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(".",end=" ")
- 它做了什么?
- 它在该行上打印第二个点,以空格结尾。
- 下一行是什么?
- 没有缩进变化,所以它是:
number = number - 1
- 它做了什么?
- 它取
number
的当前值(4),从中减去 1,得到 3,最后将 3 作为number
的新值。 - 下一行是什么?
- 由于 while 循环结束导致了缩进变化,所以下一行是:
while number > 1:
- 它做了什么?
- 它将
number
的当前值(3)与 1 进行比较。3 > 1
,所以 while 循环继续。 - 下一行是什么?
- 由于 while 循环条件为真,所以下一行是:
print(".",end=" ")
- 它做了什么?
- 它在该行上打印第三个点。
- 下一行是什么?
- 它是:
number = number - 1
- 它做了什么?
- 它取
number
的当前值(3),从中减去 1,并将 2 作为number
的新值。 - 下一行是什么?
- 回到 while 循环的开头:
while number > 1:
- 它做了什么?
- 它将
number
的当前值(2)与 1 进行比较。由于2 > 1
,所以 while 循环继续。 - 下一行是什么?
- 由于 while 循环正在继续:
print(".",end=" ")
- 它做了什么?
- 它发现了生命、宇宙和万物的意义。我开玩笑的。(我必须确保你醒着。)这一行在屏幕上打印第四个点。
- 下一行是什么?
- 它是:
number = number - 1
- 它做了什么?
- 取
number
的当前值(2),从中减去 1,并将 1 作为number
的新值。 - 下一行是什么?
- 回到 while 循环:
while number > 1:
- 这一行做了什么?
- 它将
number
的当前值(1)与 1 进行比较。由于1 > 1
为假(1 不大于 1),所以 while 循环退出。 - 下一行是什么?
- 由于 while 循环条件为假,所以下一行是 while 循环退出后的下一行,即:
print()
- 那行做了什么?
- 让屏幕进入下一行。
- 为什么该程序没有打印 5 个点?
- 循环提前退出 1 个点。
- 我们如何解决这个问题?
- 让循环延迟 1 个点退出。
- 我们如何做到这一点?
- 有几种方法。一种方法是将 while 循环更改为:
while number > 0:
另一种方法是将条件更改为:number >= 1
还有其他一些方法。
你需要弄清楚程序在做什么。你需要弄清楚程序应该做什么。找出两者之间的差异。调试是一项需要练习才能掌握的技能。如果你在一个小时后仍然无法解决问题,那就休息一下,和别人谈谈这个问题,或者沉思你肚脐里的绒毛。过一会儿再回来,你可能会对这个问题有新的想法。祝你好运。