Python学习:异常处理,提高程序的稳定性

tech2022-08-03  184

和其他语言一样,异常处理是 Python 中一种很常见,并且很重要的机制与代码规范。

一、异常与错误

通常来说,程序中的错误至少包括两种,一种是语法错误,另一种则是异常。 所谓语法错误,也就是你写的代码不符合编程规范,无法被识别与执行,比如下面这个例子:

if name is not None print(name)

If 语句漏掉了冒号,不符合 Python 的语法规范,所以程序就会报错invalid syntax。

而异常则是指程序的语法正确,也可以被执行,但在执行过程中遇到了错误,抛出了异常,比如下面的 3 个例子:

10 / 0 Traceback (most recent call last): File "<stdin>", line 1, in <module> ZeroDivisionError: integer division or modulo by zero order * 2 Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'order' is not defined 1 + [1, 2] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for +: 'int' and 'list'

它们语法完全正确,但显然,我们不能做除法时让分母为 0;也不能使用未定义的变量做运算;而让一个整型和一个列表相加也是不可取的。

异常是什么?

异常即是一个事件,该事件会在程序执行过程中发生,影响了程序的正常执行。

一般情况下,在Python无法正常处理程序时就会发生一个异常。

异常是Python对象,表示一个错误。

当Python脚本发生异常时我们需要捕获处理它,否则程序会终止执行。

二、异常处理

捕捉异常可以使用try/except语句。

try/except语句用来检测try语句块中的错误,从而让except语句捕获异常信息并处理。

如果你不想在异常发生时结束你的程序,只需在try里捕获它。

以下为简单的try…except…else的语法:

try: <语句> #运行别的代码 except <名字><语句> #如果在try部份引发了'name'异常 except <名字><数据>: <语句> #如果引发了'name'异常,获得附加的数据 else: <语句> #如果没有异常发生

下面是个简单的例子:

try: s = input('please enter two numbers separated by comma: ') num1 = int(s.split(',')[0].strip()) num2 = int(s.split(',')[1].strip()) ... except ValueError as err: print('Value Error: {}'.format(err)) print('continue') ...

这里默认用户输入以逗号相隔的两个整形数字,将其提取后,做后续的操作(注意 input 函数会将输入转换为字符串类型)。如果我们输入a,b,程序便会抛出异常invalid literal for int() with base 10: ‘a’,然后跳出 try 这个 block。

由于程序抛出的异常类型是 ValueError,和 except block 所 catch 的异常类型相匹配,所以 except block 便会被执行,最终输出Value Error: invalid literal for int() with base 10: ‘a’,并打印出continue。

please enter two numbers separated by comma: a,b Value Error: invalid literal for int() with base 10: 'a' continue

except block 只接受与它相匹配的异常类型并执行,如果程序抛出的异常并不匹配,那么程序照样会终止并退出。

所以,还是刚刚这个例子,如果我们只输入1,程序抛出的异常就是IndexError: list index out of range,与 ValueError 不匹配,那么 except block 就不会被执行,程序便会终止并退出(continue 不会被打印)。

please enter two numbers separated by comma: 1 IndexError Traceback (most recent call last) IndexError: list index out of range

其中一种解决方案,是在 except block 中加入多种异常的类型,比如下面这样的写法:

try: s = input('please enter two numbers separated by comma: ') num1 = int(s.split(',')[0].strip()) num2 = int(s.split(',')[1].strip()) ... except (ValueError, IndexError) as err: print('Error: {}'.format(err)) print('continue') ...

或者第二种写法:

try: s = input('please enter two numbers separated by comma: ') num1 = int(s.split(',')[0].strip()) num2 = int(s.split(',')[1].strip()) ... except ValueError as err: print('Value Error: {}'.format(err)) except IndexError as err: print('Index Error: {}'.format(err)) print('continue') ...

很多时候,很难保证程序覆盖所有的异常类型,所以,更通常的做法,是在最后一个 except block,声明其处理的异常类型是 Exception。Exception 是其他所有非系统异常的基类,能够匹配任意非系统异常。

try: s = input('please enter two numbers separated by comma: ') num1 = int(s.split(',')[0].strip()) num2 = int(s.split(',')[1].strip()) ... except ValueError as err: print('Value Error: {}'.format(err)) except IndexError as err: print('Index Error: {}'.format(err)) except Exception as err: print('Other error: {}'.format(err)) print('continue') ...

或者,可以在 except 后面省略异常类型,这表示与任意异常相匹配(包括系统异常等)

try: s = input('please enter two numbers separated by comma: ') num1 = int(s.split(',')[0].strip()) num2 = int(s.split(',')[1].strip()) ... except ValueError as err: print('Value Error: {}'.format(err)) except IndexError as err: print('Index Error: {}'.format(err)) except: print('Other error') print('continue') ...

注意,当程序中存在多个 except block 时,最多只有一个 except block 会被执行。换句话说,如果多个 except 声明的异常类型都与实际相匹配,那么只有最前面的 except block 会被执行,其他则被忽略。

异常处理中,还有一个很常见的用法是 finally,经常和 try、except 放在一起来用。无论发生什么情况,finally block 中的语句都会被执行,哪怕前面的 try 和 excep block 中使用了 return 语句。

try: <语句> finally: <语句> #退出try时总会执行 raise

文件读取的例子:

import sys try: f = open('file.txt', 'r') .... # some data processing except OSError as err: print('OS error: {}'.format(err)) except: print('Unexpected error:', sys.exc_info()[0]) finally: f.close()

这段代码中,try block 尝试读取 file.txt 这个文件,并对其中的数据进行一系列的处理,到最后,无论是读取成功还是读取失败,程序都会执行 finally 中的语句——关闭这个文件流,确保文件的完整性。因此,在 finally 中,我们通常会放一些无论如何都要执行的语句。

值得一提的是,对于文件的读取,我们也常常使用 with open,你也许在前面的例子中已经看到过,with open 会在最后自动关闭文件,让语句更加简洁。

异常的参数:

一个异常可以带上参数,可作为输出的异常信息参数。

你可以通过except语句来捕获异常的参数,如下所示:

try: 正常的操作 ...................... except ExceptionType, Argument: 你可以在这输出 Argument 的值...

变量接收的异常值通常包含在异常的语句中。在元组的表单中变量可以接收一个或者多个值。

元组通常包含错误字符串,错误数字,错误位置。

# 定义函数 def temp_convert(var): try: return int(var) except ValueError, Argument: print "参数没有包含数字\n", Argument # 调用函数 temp_convert("xyz"); 参数没有包含数字 invalid literal for int() with base 10: 'xyz'

触发异常:

我们可以使用raise语句自己触发异常

raise语法格式如下:

raise [Exception [, args [, traceback]]]

语句中 Exception 是异常的类型(例如,NameError)参数标准异常中任一种,args 是自已提供的异常参数。

最后一个参数是可选的(在实践中很少使用),如果存在,是跟踪异常对象。

一个异常可以是一个字符串,类或对象。 Python的内核提供的异常,大多数都是实例化的类,这是一个类的实例的参数。定义一个异常非常简单,如下所示:

def functionName( level ): if level < 1: raise Exception("Invalid level!", level) # 触发异常后,后面的代码就不会再执行

注意:为了能够捕获异常,"except"语句必须有用相同的异常来抛出类对象或者字符串。

def mye( level ): if level < 1: raise Exception,"Invalid level!" # 触发异常后,后面的代码就不会再执行 try: mye(0) # 触发异常 except Exception,err: print 1,err else: print 2 Invalid level!

三、用户自定义异常

通过创建一个新的异常类,程序可以命名它们自己的异常。异常应该是典型的继承自Exception类,通过直接或间接的方式。

下面这个例子,我们创建了自定义的异常类型 MyInputError,定义并实现了初始化函数和 str 函数(直接 print 时调用):

class MyInputError(Exception): """Exception raised when there're errors in input""" def __init__(self, value): # 自定义异常类型的初始化 self.value = value def __str__(self): # 自定义异常类型的 string 表达形式 return ("{} is invalid input".format(repr(self.value))) try: raise MyInputError(1) # 抛出 MyInputError 这个异常 except MyInputError as err: print('error: {}'.format(err))

如果你执行上述代码块并输出,便会得到下面的结果:

error: 1 is invalid input

实际工作中,如果内置的异常类型无法满足我们的需求,或者为了让异常更加详细、可读,想增加一些异常类型的其他功能,我们可以自定义所需异常类型。不过,大多数情况下,Python 内置的异常类型就足够好了。

四、异常的使用场景与注意点

通常来说,在程序中,如果我们不确定某段代码能否成功执行,往往这个地方就需要使用异常处理。除了上述文件读取的例子,我可以再举一个例子来说明。

大型社交网站的后台,需要针对用户发送的请求返回相应记录。用户记录往往储存在 key-value 结构的数据库中,每次有请求过来后,我们拿到用户的 ID,并用 ID 查询数据库中此人的记录,就能返回相应的结果。

而数据库返回的原始数据,往往是 json string 的形式,这就需要我们首先对 json string 进行 decode(解码),你可能很容易想到下面的方法:

import json raw_data = queryDB(uid) # 根据用户的 id,返回相应的信息 data = json.loads(raw_data)

要知道,在 json.loads() 函数中,输入的字符串如果不符合其规范,那么便无法解码,就会抛出异常,因此加上异常处理十分必要。

try: data = json.loads(raw_data) .... except JSONDecodeError as err: print('JSONDecodeError: {}'.format(err))

注:不能滥用异常处理。

总结

异常,通常是指程序运行的过程中遇到了错误,终止并退出。我们通常使用 try except语句去处理异常,这样程序就不会被终止,仍能继续执行。处理异常时,如果有必须执行的语句,比如文件打开后必须关闭等等,则可以放在 finally block 中。异常处理,通常用在你不确定某段代码能否成功执行,也无法轻易判断的情况下,比如数据库的连接、读取等等。正常的 flow-control 逻辑,不要使用异常处理,直接用条件语句解决就可以了。

参考: 《Python核心技术与实践》 《Python 异常处理 | 菜鸟教程》

最新回复(0)