python之异常处理

172 阅读5分钟

错误与异常

首先要了解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;也不能使用未定义的变量做运算;而让一个整型和一个列表相加也是不可取的。

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

当然,python中还有很多其他异常类型,比如KeyError是指字典中的键找不到;FileNotFoundError是指发送了读取文件的请求,但相应的文件不存在等等。

如何处理异常

如果执行程序中某处抛出了异常,程序就会被终止并退出。为了让其照样运行下去,通常使用try和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))

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

不过,很多时候,我们很难保证程序覆盖所有的异常类型,所以更通常的做法,是在最后一个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和except 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中的语句----关闭这个文件流,确保文件的完整性。因此,在finall中,我们通常都会放一些无论如何都要执行的语句。

用户自定义异常

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

class MyInputError(Exception):
      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