3.5 异常处理
1.恼人的bug
bug一定是程序员最痛恨的生物了。程序员眼中的bug,是指程序缺陷。这些程序缺陷会引发错误或者意想不到的后果。很多时候,程序bug可以事后修复。当然,也存在无法修复的教训。欧洲ARIANE 5火箭第一次发射时,在一分钟之内爆炸。事后调查原因,发现导航程序中的一个浮点数要转换成整数,但由于数值过大溢出。此外,英国直升机于1994年坠毁,29人死亡。调查显示,直升机的软件系统“充满缺陷”。而电影《2001太空漫游》中,超级计算机HAL杀死了几乎所有的宇航员,原因是HAL程序中的两个目标出现了冲突。
在英文中,bug是虫子的意思。工程师很早就开始用bug这个词来指代机械缺陷。而软件开发中使用bug这个词,还有一个小故事。曾经有一只蛾子飞进一台早期计算机,造成这台计算机出错。从那以后,bug就被用于指代程序缺陷。这只蛾子后来被贴在日志本上,至今还在美国国家历史博物馆展出。
很多程序缺陷都可以很早发现并改正。比如,下面程序错用了语法,在for的一行没有加引号。
for i in range(10)
print(i)
Python不会运行这段程序。它会提醒你有语法错误:
SyntaxError: invalid syntax
下面的程序并没有语法上的错误,但在Python运行时,会发现引用的下标超出了列表元素的范围:
a = [1, 2, 3]
print(a[3])
程序会中止报错:
IndexError: list index out of range
上面这种只有在运行时,编译器才会发现的错误被称为运行时错误(Runtime Error)。由于Python是动态语言,许多操作必须在运行时才会执行,比如确定变量的类型等。因此,Python要比静态语言更容易产生运行时错误。
还有一种错误,称为语义错误(Semantic Error)。编译器认为你的程序没有问题,可以正常运行。但当检查程序时,却发现程序并非你想做的。通常来说,这种错误最为隐蔽,也最难纠正。比如下面这个程序,目的是打印列表的第一个元素:
bundle = ["a", "b", "c"]
print(bundle[1])
程序并没有错误,正常打印。但你发现,打印出的是第二个元素"b",而不是第一个元素。这是因为Python列表的下标是从0开始的,所以引用第一个元素,下标应该是0而不是1。
2.Debug
修改程序缺陷的过程称为debug。计算机程序具有确定性,所以错误的产生总会有其根源。当然,有时花大量时间都不能debug一段程序,确实会产生强烈的挫败感,甚至认为自己不适合做程序开发。还有的人怒摔键盘,认为电脑在玩自己。就我个人的观察来说,再优秀的程序员,在写程序时也总会产生bug。只不过,优秀的程序员在debug的过程中更心平气和,不会因为bug而质疑自己。他们甚至会把debug的过程当作一种训练,通过更好地理解错误根源来让自己的计算机知识更上一层楼。
其实,debug有点像做侦探。搜集蛛丝马迹的证据,排除清白的嫌疑人,最后留下真凶。收集证据的方法有很多,也有许多现成的工具。对于初学者来说,不需要花太多的时间在这些工具上。在程序内部插入简单的print()函数,就可以查看变量的状态以及运行进度。有时,还可以将某个指令替换成其他形式,看看程序结果有何变化,从而验证自己的假设。当其他可能性都排除了,那么剩下的就是导致错误的真正的原因。
从另一个方面来看,debug也是写程序的一个自然部分。有一种开发程序的方式,就是测试驱动开发(Test-Driven Development,TDD)。对于Python这样一种便捷的动态语言来说,很适合先写一个小型的程序,实现特定的功能。然后,在小程序的基础上,渐进地修改,让程序不断进化,最后满足复杂的需求。整个过程中,你不断增加功能,也不断改正某些错误。重要的是,你一直在动手编程。Python作者本人就很喜欢这种编程方式。因此,debug其实是你写出完美程序的一个必要步骤。
3.异常处理
对于运行时可能产生的错误,我们可以提前在程序中处理。这样做有两个可能的目的:一个是让程序中止前进行更多的操作,比如提供更多的关于错误的信息。另一个则是让程序在犯错后依然能运行下去。
异常处理还可以提高程序的容错性。下面的一段程序就用到了异常处理:
while True:
inputStr = input("Please input a number:") # 等待输入
try:
num = float(inputStr)
print("Input number:", num)
print("result:", 10/num)
exceptValueError:
print("Illegal input.Try Again.")
exceptZeroDivisionError:
print("Illegal devision by zero.Try Again.")
需要异常处理的程序包裹在try结构中。而except说明了当特定错误发生时,程序应该如何应对。程序中,input()是一个内置函数,用来接收命令行的输入。而float()函数则用于把其他类型的数据转换为浮点数。如果输入的是一个字符串,如"p",则将无法转换成浮点数,并触发ValueError,而相应的except就会运行隶属于它的程序。如果输入的是0,那么除法的分母为0,将触发ZeroDivisionError。这两种错误都由预设的程序处理,所以程序运行不会中止。
如果没有发生异常,比如输入5.0,那么try部分正常运行,except部分被跳过。异常处理完整的语法形式为:
try:
...
except exception1:
...
except exception2:
...
else:
...
finally:
...
如果try中有异常发生时,将执行异常的归属,执行except。异常层层比较,看是否是exception1、exception2……直到找到其归属,执行相应的except中的语句。如果try中没有异常,那么except部分将跳过,执行else中的语句。
finally是无论是否有异常,最后都要做的一些事情。
如果except后面没有任何参数,那么表示所有的exception都交给这段程序处理,比如:
while True:
inputStr = input("Please input a number:")
try:
num = float(inputStr)
print("Input number:", num)
print("result:", 10/num)
except:
print("Something Wrong.Try Again.")
如果无法将异常交给合适的对象,那么异常将继续向上层抛出,直到被捕捉或者造成主程序报错,比如下面的程序:
def test_func():
try:
m = 1/0
exceptValueError:
print("Catch ValueError in the sub-function")
try:
test_func()
exceptZeroDivisionError:
print("Catch error in the main program")
子程序的try...except...结构无法处理相应的除以0的错误,所以错误被抛给上层的主程序。
使用raise关键字,我们也可以在程序中主动抛出异常。比如:
raiseZeroDivisionError()
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论