在使用Scratch编程时,我们基本上不会遇到什么“错误”,除了极少数的情况(比如你调用了没有退出条件的递归函数),Scratch本身会出现问题之外,我们的程序不会说因为你写错了一点东西就停止运行,最多给你返回和预期不同的结果(比如,你非要让一个字符串和数字相加,Scratch只好忽略字符串)。毕竟Scratch面向的是刚刚开始学编程的孩子们,如果动不动就抛出一个错误,让程序停止运行,估计会让很多人放弃学习,这也是一种妥协的结果。
学了Python,大家的感觉就不同了,你发现Python的“脾气”没有Scratch那么好,你写错了一个函数的名字,Python会报告标识符未定义的错误;你少写了一个空格导致缩进问题,Python干脆不运行你的程序;你在运行的时候要把输入的汉字转换为数值,Python毫不客气地停止程序执行;你调用函数时参数写错了……总之,Python就像一位严格的老师,对约定的细节真是一丝不苟。
有的同学会感觉头疼,甚至觉得太难不想坚持下去。其实这是没有必要的。编程本身就是一件对逻辑要求非常严格的事情,绝不允许一丝一毫的马虎大意,这对我们思维的严密性、逻辑的严谨性都是很好的锻炼,只有迈过这个坎,你才真正走上了编程之路。
这一节,我们就来学习一下,如何应对编程中出现的各类错误。在Python中,这些错误统称为“异常”。
一、常见的异常
我们经常见到的异常可以分为两类,一类是语法的错误。如果你写错了,Python程序根本就不能执行。比如下面这段代码:
print('hello')
print('world')
第二行代码之前多了一个空格,也就是有缩进的错误。运行结果是:
这个错误的最后一条信息是:
IndentationError: unexpected indent
翻译过来就是缩进错误的意思。你可以留意一下这类信息,慢慢熟悉了之后,一看就知道自己的程序出的是什么类型的问题。
第二类就不那么容易发现了:
a = int(input('请输入一个数字:'))
if a > 0:
print('你输入的是正数')
else:
pint('你输入的不是正数')
发现问题了吗?如果运行程序,输入一个正数,程序在控制台打印出“你输入的是正数”,完美!不过你如果输入的是0或负数,就会出错:
最后一行是:
NameError: name ‘pint’ is not defined
原来,你由于粗心把print()函数写成了pint(),Python并不在启动时检查这种错误,所以它还可以运行,甚至在某些情况下得到正确的结果。但在某些情况下程序要运行有问题的那部分代码时,就出错了。其实上面这段程序还隐藏着另一个问题:
这次我输入的不是数字,是字母“a”,Python报了下面的异常:
ValueError: invalid literal for int() with base 10: ‘a’
这个意思是“值错误:无效的十进制数’a’。原来,int函数要求你输入的内容必须是能转换为整数的数值,你输入的字母无法转换,参数无效,就出错了。
如何应对这些异常呢?首先你要会解读异常信息的提示。
二、异常信息解析
为了更好地说明异常信息,我们把上面的程序稍微改动一下,变成函数调用的形式:
def check_number():
a = int(input('请输入一个数字:'))
if a > 0:
print('你输入的是正数')
else:
pint('你输入的不是正数')
check_number()
运行上面的代码,输入a,程序报出异常:
重点关注绿色框内的信息:
第一个绿色框内表示在文件“正数判断.py”这个Python文件中第9行check_number()时发生了错误,这个错误是怎么引发的呢?继续向下看,就看到原来还是同一个文件中的第三行输入数字引发的。如果第三行还调用了其它函数,Python还会再给出进一步的信息。总之,从第一个绿色框向下到第二个绿色框之间,反映的是出错代码的调用关系,你可以“顺藤摸瓜”找到出错的根源。
当然,从上面的截图可以看到,海龟编辑器提供了更友好的功能,直接把第三行代码标为红色,告诉你:错误就在这一行上。很多编辑器都有这种功能,但我们也要学会从异常信息中追根溯源。
第二个绿色框内,就是具体异常信息的提示了。“ValueError”表示参数无效错误,后面那句话说明具体是什么参数错误,刚才我们已经解释过了,你输入的字符“a”不能转换为十进制数字。
了解了异常信息的结构,我们再列出一些常见的异常:
异常名称 | 描述 |
---|---|
FloatingPointError | 浮点计算错误 |
OverflowError | 数值运算超出最大限制 |
ZeroDivisionError | 除(或取模)零 (所有数据类型) |
IOError | 输入/输出操作失败 |
OSError | 操作系统错误 |
IndexError | 序列中没有没有此索引(index)【越界】 |
KeyError | 映射中没有这个键 |
NameError | 未声明/初始化对象 (没有属性) |
RuntimeError | 一般的运行时错误 |
SyntaxError | Python 语法错误 |
IndentationError | 缩进错误 |
TabError | Tab 和空格混用 |
TypeError | 对类型无效的操作 |
ValueError | 传入无效的参数 |
上面列出的并不是全部的异常类型,这个类型也不需要记忆,只要了解一下,以后见得多了,也就熟悉了。如果遇到没见过的类型,可以在搜索引擎搜索异常类型的名字(如“ValueError”)即可。
三、基本的异常处理
编程中遇到异常,先不要着急,静下心来分析异常信息,定位到有问题的代码,一般可以解决,让程序正常运行。不过不管我们如何细致,总会有一些运行时才出现的异常,你根本无法预料让用户输入数字的时候,他会不会输入一个字符进来。这种情况下,我们就要分析程序运行时可能发生的已知或未知的错误,在程序在做出预防措施,这种预防措施就是异常捕获。
为了捕获并处理异常,Python提供了try…except语句:
try:
<可能异常的语句>
except:
<处理异常>
比如在上面的例子中,你预料用户有可能会输入一个不是数字的字符串导致程序异常,我们就可以使用try…except语句改写它(注意try和except后面的语句都需要缩进):
def check_number():
try:
a = int(input('请输入一个数字:'))
if a > 0:
print('你输入的是正数')
else:
print('你输入的不是正数')
except:
print('你输入的数字无效')
check_number()
这次,你输入错误的数字后,程序会友好地告诉你:你输入的数字无效,不会再抛出异常后中断程序了。需要注意的是,except后面的代码要小心,尽可能简单,别让except后的语句块再发生了异常,那程序还是会中止。
以上这种写法只是给我们提供了一种“优雅”地退出程序的方式,如果你要在用户输入无效数字的时候提示并让他重新输入,这就需要其它的逻辑来实现了,这里暂且不提。
四、异常处理的扩展写法
try…except语句还有两种扩展的写法,实现更完善的异常处理功能。
1、try…except…else:
try:
<可能异常的语句>
except:
<处理异常>
else:
<语句块>
这个语句的意思是,如果try语句中的代码块没有发生任何异常,则会执行else语句中的代码块。
例如:
def check_number():
try:
a = int(input('请输入一个数字:'))
except:
print('你输入的数字无效')
else:
if a > 0:
print('你输入的是正数')
else:
print('你输入的不是正数')
check_number()
2、try…except…finally
完整的异常处理语句还可以包括finally语句块,无论程序是否发生异常,finally中的代码块都会被执行。
try:
<可能异常的语句>
except:
<处理异常>
finally:
<语句块>
例如:
def check_number():
try:
a = int(input('请输入一个数字:'))
if a > 0:
print('你输入的是正数')
else:
print('你输入的不是正数')
except:
print('你输入的数字无效')
finally:
print('函数执行结束')
check_number()
在这个例子中,我们是简单地在finally后加了一个信息提示。实际应用中,可能会用finally做一些清理工作。比如在代码中打开并处理文件时,为了防止发生异常时文件来不及关闭造成错误,我们就可以把关闭文件的代码写在finally中。
五、课后作业
编写程序,输入两个数字保存到x、y,将x、y转换为float类型并输出x/y的值。要求: 1、程序可能发生数据转换错误或被零除错误,如果发生异常,程序要能够给予提示; 2、无论是否发生异常,程序要在控制台输出“计算结束”。