Python核心异常处理

Posted sysu_lluozh

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python核心异常处理相关的知识,希望对你有一定的参考价值。

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

一、错误与异常

首先要了解,Python中的错误和异常是什么?两者之间又有什么联系和区别呢?

1.1 错误类型

通常来说,程序中的错误至少包括两种:

  • 一种是语法错误
  • 一种是异常

1.2 语法错误

所谓语法错误,也就是代码不符合编程规范,无法被识别与执行,比如下面这个例子:

if name is not None
    print(name)

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

1.3 异常

而异常则是指程序的语法正确,也可以被执行,但在执行过程中遇到错误抛出了异常,比如下面的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
  • 不能使用未定义的变量做运算
  • 不能一个整型和一个列表相加

于是,当程序运行到这些地方时,就抛出了异常并且终止运行。例子中的ZeroDivisionErrorNameErrorTypeError,就是三种常见的异常类型

当然,Python 中还有很多其他异常类型,比如:

  • KeyError是指字典中的键找不到
  • FileNotFoundError是指发送了读取文件的请求,但相应的文件不存在

详细的异常类型可参考相应文档

二、如何处理异常

2.1 异常捕获

如果执行到程序中某处抛出了异常,程序就会被终止并退出。那有没有什么办法可以不终止程序,让其照样运行下去呢?答案当然是肯定的,这也就是所说的异常处理,通常使用tryexcept来解决,比如:

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

2.2 多种异常处理

由于程序抛出的异常类型是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类型与实际匹配即可

2.3 覆盖所有异常

不过,很多时候很难保证程序覆盖所有的异常类型,所以,更通常的做法是在最后一个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会被执行,其他则被忽略

2.4 finally

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

一个常见的应用场景,便是文件的读取:

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 openwith open会在最后自动关闭文件,让语句更加简洁

三、用户自定义异常

前面的例子里充斥了很多Python内置的异常类型,那可以创建自己的异常类型吗?

答案是肯定的,Python当然允许这么做。下面这个例子,创建了自定义的异常类型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内置的异常类型就已足够

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

讲完了前面的基础知识,接下来着重谈一下异常的使用场景与注意点

4.1 使用场景

通常来说,在程序中如果不确定某段代码能否成功执行,往往这个地方就需要使用异常处理

除了上述文件读取的例子,再举一个例子来说明:

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

4.2 勿滥用异常处理

不过,有一点切记,不能走向另一个极端——滥用异常处理

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

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

诚然,这样的代码并没有bug,但是让人看了摸不着头脑,也显得很冗余
如果代码中充斥着这种写法,无疑对阅读、协作来说都是障碍。因此,对于flow-control(流程控制)的代码逻辑,一般不用异常处理

字典这个例子可以这样写:

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

五、总结

主要讲解Python的异常处理及其使用场景,需要重点掌握下面几点

  • 异常,通常是指程序运行的过程中遇到了错误,终止并退出
    通常使用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) as 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后面的错误类型先抛出数据库连接错误,之后才抛出查询错误,实现的异常处理和第二种一样

以上是关于Python核心异常处理的主要内容,如果未能解决你的问题,请参考以下文章

CSP核心代码片段记录

PCL异常处理:pcl 1.8.13rdpartyoostincludeoost-1_64oost ypeofmsvc ypeof_impl.hpp(125): error(代码片段

在 Python 多处理进程中运行较慢的 OpenCV 代码片段

python常用代码片段总结

使用片段中的处理程序时出现非法状态异常

你如何在 python 中处理 graphql 查询和片段?