异常处理

异常处理

错误与异常

  • 程序中的错误至少包括两种,一种是语法错误(invalid syntax),另一种则是异常
  • 异常则是指程序的语法正确,也可以被执行,但在执行过程中遇到了错误,抛出了异常。
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'

例子中的ZeroDivisionError,NameErrorTypeError,就是三种常见的异常类型。

其他错误具体参考文档

处理异常

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')
...


please enter two numbers separated by comma: a,b
Value Error: invalid literal for int() with base 10: 'a'
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。

  • 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 类型与实际匹配即可。

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

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 会被执行,其他则被忽略。
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 中,通常会放一些无论如何都要执行的语句。

用户自定义异常

创建自定义的异常类型 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

异常的使用场景与注意点

  • 不确定某段代码能否成功执行,往往这个地方就需要使用异常处理。

大型社交网站的后台,需要针对用户发送的请求返回相应记录。用户记录往往储存在 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))
  • 不能滥用异常

当想要查找字典中某个键对应的值时,绝不能写成下面这种形式:

d = {'name': 'jason', 'age': 20}
try:
    value = d['dob']
    ...
except KeyError as err:
    print('KeyError: {}'.format(err))

对于 flow-control(流程控制)的代码逻辑,我们一般不用异常处理。

字典推荐写法:

if 'dob' in d:
    value = d['dob']
    ...

总结

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

思考题

在异常处理时,如果 try block 中有多处抛出异常,需要我们使用多个 try except block 吗?以数据库的连接、读取为例,下面两种写法,你觉得哪种更好呢?

第一种:

try:
    db = DB.connect('<db path>') # 可能会抛出异常
    raw_data = DB.queryData('<viewer_id>') # 可能会抛出异常
except (DBConnectionError, DBQueryDataError) err:
    print('Error: {}'.format(err))

第二种:

try:
    db = DB.connect('<db path>') # 可能会抛出异常
    try:
        raw_data = DB.queryData('<viewer_id>')
    except DBQueryDataError as err:
         print('DB query data error: {}'.format(err))
except DBConnectionError as err:
     print('DB connection error: {}'.format(err))

答案:

第一种写法更加简洁,易于阅读。而且except后面的错误类型先抛出数据库连接错误,之后才抛出查询错误,实现的异常处理和第二种一样。 ①与②的运行逻辑一致,①可以看作②的简化版;②的写法,如出现异常,多次调用异常处理,降低程序运行效率;

赞赏
Nemo版权所有丨如未注明,均为原创丨本网站采用BY-NC-SA协议进行授权,转载请注明转自:https://nemo.cool/489.html

Nemo

文章作者

推荐文章

发表回复

textsms
account_circle
email

异常处理
错误与异常 程序中的错误至少包括两种,一种是语法错误(invalid syntax),另一种则是异常。 异常则是指程序的语法正确,也可以被执行,但在执行过程中遇到了错误,抛出了异常。 10 / …
扫描二维码继续阅读
2019-11-27