3.2 错误与异常
代码是人的逻辑思维的具体体现,因为没有一个人的逻辑思维是完美无缺的,所以人在编写代码时必然会出现各种错误。既然错误或多或少都会发生,那么如何捕捉错误,并且捕捉到错误后要如何处理,就显得很重要。
3.2.1 语法错误
在编写Python程序时,遇到的错误可以分为两大类,一类是语法错误,另一类是运行时错误。
Python解释器在解析Python代码时,如果发现无法解析的语句,就会抛出SyntaxError语法错误。Python解释器之所以无法解析,是因为当前的Python代码没有完全符合Python的规则,代码如下:
在Python2中,可以使用“print'你好'”的形式输出内容,但这种语法规则在Python3中已不被支持。Python3的解释器无法解析“print '你好'”这条语句,此时就会抛出SyntaxError语法错误,从错误对应的提示中也可以看出,Python3解释器希望使用“print('你好')”。
每种编程语言都有具体的语法规则,如果不按照语法规则编写代码,就会出现语法错误。想要避免这种错误,只需要将代码修改成符合语法规则的语句即可。
运行时错误与语法错误不同,它是指代码的语法规则都是正确的,但在运行时出现了错误,运行时错误也被称为“程序抛出了异常”。后续两节中我们将进一步介绍运行时错误以及处理方式。
3.2.2 异常捕捉
异常是相对正常而言的,异常是指某个事件发生在程序执行过程中并影响程序正常的执行流程,使得程序本身无法正常处理该事件,导致程序终止。
为了避免一些“可预测异常”影响程序的正常执行,Python提供了try…except…语句来捕获异常,try关键字下的代码块在执行时如果出现了错误,会触发except关键字捕获异常信息并通过该关键字下的代码块进行处理。示例代码如下:
上述代码将整型变量a与字符串型变量b相加,因为整型变量与字符串型变量不可直接相加,所以此时会产生错误,抛出异常。因为相加语句在try关键字的代码块中,所以产生的异常会触发except关键字并执行except关键字下的代码块。
Python中定义了很多标准异常用于表明某种错误,表3.1简单列举了常见的几种异常类型。
表3.1 常见的几种异常类型
还有很多标准异常,篇幅原因,本节不进行全部展示。
可以使用except关键字捕捉相应类型的异常,except关键字后可以接相应的异常类型,当try代码块中的代码报错时,只有抛出与except关键字后异常类型相同的异常,才会执行except关键字后的代码逻辑;如果except关键字后没有接异常类型,那么任何异常都会触发except代码块中的代码逻辑。示例代码如下:
try…except…语句可以进一步完善成try…except…else…语句,代码如下:
如果try代码块没有抛出任何异常,则会执行else代码块中的代码;如果try代码块抛出异常,则会触发except关键字。在上述代码中,except关键字只捕捉TypeError类型的异常,而对于其他类型的异常,except代码块不会执行。
如果希望try代码块中的代码无论是否抛出异常,代码都执行某种逻辑,则可以使用finally关键字,代码如下:
读者可能会存在一些疑惑,为什么要在except关键字后接异常类型呢?处理所有的异常不是更加合理吗?
要理解上述问题,关键点是我们要理解人的逻辑不是完美的,人编写的代码也不是完美的,当代码抛出预料之外的异常,通过except关键字捕捉所有错误后,你并不知道这种异常应当如何处理,该异常的出现是意料之外的。
你可能会在except代码块中通过print方法输出一个提示,然后让程序继续运行,不至于因为意料之外的异常而导致程序崩溃。但此时程序已经处于异常状态了,它很有可能会影响后续代码逻辑的执行,从而产生错误的结果。此时从产生的错误结果来看,代码中原始错误出现的位置难以判断,因为原始错误被except关键字“隐藏”了。
为了避免这种情况的出现,建议大家只处理能处理的异常,而不是所有的异常。
3.2.3 异常处理
我们已经知道通过try…except…可以捕捉Python程序中产生的异常,但如果捕捉到的异常无法处理呢?此时最好的方式就是向上抛出异常。
异常处理的原则是处理可预见的异常,向上抛出当前无法处理的异常,最终的目的就是方便程序编写者可以很轻松地定位出现异常的位置,以及出现异常的原因。
在Python中,可以通过raise关键字实现手动抛出异常的效果,其基本语法如下:
其中,中括号“[]”括起的部分是可选参数,exceptionName的作用就是抛出指定类型的异常,而reason则是抛出异常时附带的说明信息。示例代码如下:
raise会将程序的异常信息向上抛出,如果上层有对应的处理逻辑,说明错误对上层程序而言是可预见的,此时程序可以继续运行;如果上层无法处理异常,则继续将异常信息向上抛出,直到异常无法被处理且没有上层时,程序就会因异常而停止运行。
上述描述中频繁出现“上层”的概念,上层其实就是当前层的父亲层,我们通过简单的代码来加深理解。
上述代码定义了error_fun方法,该方法使用了try…except…语句,在try代码块中使用input方法接收用户的输入,程序编写者预测到用户可能会输入全空格的内容或全数字的内容,但这都不是我们想要的,所以通过raise抛出ValueError类型的错误,而本层的except只捕捉TypeError类型的错误,ValueError类型的错误会继续向上抛出。
error_fun方法的上一层也被try…except…语句包裹,except恰好捕捉ValueError类型的错误,所以except代码块中的代码逻辑会被执行。
有时,通过简单的异常信息难以找到程序出现问题的具体原因,此时可以使用traceback库来获取更加详细的异常信息。
traceback库是Python内置库,无须安装pip便可以直接使用,下面介绍traceback库的两种常见用法。
(1)利用traceback的print_exc方法可以将详细的异常信息输出,代码如下:
上述代码创建了列表l,并通过len方法获取列表l的长度为3,随后通过下标获取列表l中的值,因为列表的下标是从0开始的,所以无法获取以列表长度作为下标的元素。以列表l为例,列表l长度为3,但列表l最后一个元素的下标为2,代码通过l[3]获取列表l中的元素时就会抛出异常。
try代码块抛出的异常会被except捕捉,except代码块中使用traceback.print_exc方法将详细的异常信息输出到屏幕上。
(2)如果希望将异常信息写入文件中以方便日后查看,可以使用traceback的format_exc方法,代码如下:
traceback.format_exc方法会获取格式化后的异常信息,在代码中通过open方法打开一个文件,然后将异常信息写入文件中。open方法接收两个参数,第一个为打开文件的路径;第二个为写入方式,这里使用a,表示以追加的形式将信息写入文件中。