跳转到内容

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 = ,然后它打印变量 widthheight 中包含的值的总和,从 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 还有其他一些方法。

如何修复我的程序?

[编辑 | 编辑源代码]

你需要弄清楚程序在做什么。你需要弄清楚程序应该做什么。找出两者之间的差异。调试是一项需要练习才能掌握的技能。如果你在一个小时后仍然无法解决问题,那就休息一下,和别人谈谈这个问题,或者沉思你肚脐里的绒毛。过一会儿再回来,你可能会对这个问题有新的想法。祝你好运。

Python 3 非程序员教程
 ← 决策 调试 定义函数 → 
华夏公益教科书