Python 进阶之错误捕获与异常处理

一、异常介绍

Python 中的异常是一种由 raise 语句自动创建的对象。在异常对象生成之后,Python 程序不会再继续执行 raise 后面紧跟的语句或者操作异常对象本身,而是“搜索”用于处理该异常的某个特定的 handler
如果该 handler 被 Python 程序找到,则它可以关联并访问异常对象获取更多的信息;
如果没有找到与异常对应的 handler,则程序终止并输出错误信息。

PS: LBYL 与 EAFP
从理念上讲,Python 倾向于在错误发生之后通过捕获异常来处理程序错误。称为 easier to ask forgiveness than permission (EAFP)
另外一种错误处理的方式则是尽可能地在错误发生之前检查所有可能发生的情况,这种模式称为 look before you leap (LBYL)

Python 提供多种不同类型的异常用以反映错误产生的原因和场景等。每种类型的异常实际上都是一个 Python 类,且其中大多数都继承于 Exception 。对于用户自定义的异常类,也最好作为 Exception 的子类来实现。

Python 异常触发时通常会输出以下内容:

1
2
3
4
5
>>> alist = [1,2,3]
>>> alist[7]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range

上面代码中的 alist[7] 在请求列表中的项目时超出了列表原本的长度,因此触发了 IndexError 异常。该异常被 Python 交互解释器获取并处理,最终输出错误信息。

如果需要,其实也可以在代码中通过 raise 语句手动触发异常:

1
2
3
4
>>> raise IndexError("Just kidding")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: Just kidding

二、异常捕获

能够终止程序的运行并输出错误信息并不是异常机制的关键所在。异常机制的特殊性在于,通过定义合适的用于处理特定异常的 handler,可以确保一般的异常情况能够被 handler 捕捉,而不会直接导致程序运行失败。
handler 可以输出错误信息给用户,或者尝试修复问题,但是重点在于它不会终止程序。

捕获异常的基本语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
try:
body
except exception_type1 as var1:
exception_code1
except exception_type2 as var2:
exception_code2
...
except:
default_exception_code
else:
else_body
finally:
finally

其具体的执行流程如下:

  1. try 语句首先被执行,如果执行成功(没有抛出异常),则继续执行 else 语句(如果有),try 语句此时结束。最后执行 finally 语句(如果有)。
  2. 如果 try 语句执行时抛出异常,则 except 语句根据异常类型进行匹配。如某个 except 语句最终匹配抛出的异常类型,则抛出的异常赋值给其后的变量 var(如果有),对应的 exception_code 被执行。
  3. 如果 try 抛出的异常没有任何 except 语句进行匹配,则该异常继续传递看是否有内嵌的 try 语句对其进行处理。
  4. 上面格式中最后的 except 语句没有跟任何异常类型,则表示它将匹配关联所有的异常类型。这种方式在调试和创建程序原型时较常用,但并不推荐(因为隐藏了异常包含的细节)。
  5. try 语句中的 else 是可选的且并不常用,它只有在 try 语句执行后未抛出任何异常的情况下才会执行。
  6. finally 语句同样是可选的,它在任何情况下最终都会执行。即便 try 语句抛出异常且没有任何 except 语句进行捕获和处理,该异常也是在 finally 语句执行后抛出给用户。
    因此 finally 语句多用于一些“清理”任务,比如读写硬盘后的关闭文件等。如:
    1
    2
    3
    4
    5
    try:
    infile = open(filename)
    data = infile.read()
    finally:
    infile.close()

三、assert 语句

assert 语句是 raise 语句的一种特殊形式,其语法格式如下:
assert expression, argument

其含义为,如果 expression 表达式的执行结果为 False 且系统变量 __debug__ 的值为 True,则抛出 AssertionError 异常和 argument变量(可选)。

系统变量 __debug__ 的值默认为 True,可以在 Python 运行时添加 -O-OO 选项将该值改为 False
因此可以在开发程序时加入 assert 语句进行调试,而程序发布时将其保留在代码中也不会产生任何影响(只需保证 __debug__False)。

1
2
3
4
5
>>> x = (1, 2, 3)
>>> assert len(x) > 5, "len(x) not > 5"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError: len(x) not > 5

四、代码示例

不捕获异常
1
2
3
4
while True:
x = int(input('Enter the first number: '))
y = int(input('Enter the second number: '))
print(x / y)

运行效果(出错后程序退出):

1
2
3
4
5
6
7
8
9
10
11
$ python exceptions.py
Enter the first number: 5
Enter the second number: 4
1.25
Enter the first number: 4
Enter the second number: 0
Traceback (most recent call last):
File "exception.py", line 4, in <module>
print(x / y)
ZeroDivisionError: division by zero
$

捕获除数不为零异常
1
2
3
4
5
6
7
while True:
try:
x = int(input('Enter the first number: '))
y = int(input('Enter the second number: '))
print(x / y)
except ZeroDivisionError:
print('Division by zero is illegal')

运行效果(出错后异常被捕获,程序不退出):

1
2
3
4
5
6
7
8
$ python exceptions.py
Enter the first number: 4
Enter the second number: 0
Division by zero is illegal
Enter the first number: 5
Enter the second number: 4
1.25
Enter the first number:

捕获多个异常
1
2
3
4
5
6
7
8
9
while True:
try:
x = int(input('Enter the first number: '))
y = int(input('Enter the second number: '))
print(x / y)
except ZeroDivisionError:
print('Division by zero is illegal')
except ValueError:
print("That wasn't a number, was it?")

运行效果:

1
2
3
4
5
6
7
8
9
10
11
$ python exceptions.py
Enter the first number: 4
Enter the second number: 0
Division by zero is illegal
Enter the first number: 4
Enter the second number: hello
That wasn't a number, was it?
Enter the first number: 5
Enter the second number: 4
1.25
Enter the first number:

捕获所有异常(不推荐,隐藏了异常的细节)
1
2
3
4
5
6
7
while True:
try:
x = int(input('Enter the first number: '))
y = int(input('Enter the second number: '))
print(x / y)
except:
print('Something wrong happened ...')

运行效果:

1
2
3
4
5
6
7
8
$ python exceptions.py
Enter the first number: 4
Enter the second number: 0
Something wrong happened ...
Enter the first number: 4
Enter the second number: hello
Something wrong happened ...
Enter the first number:

稍好一点的版本(输出异常信息):

1
2
3
4
5
6
7
while True:
try:
x = int(input('Enter the first number: '))
y = int(input('Enter the second number: '))
print(x / y)
except Exception as e:
print('Invalid input:', e)

运行效果:

1
2
3
4
5
6
7
Enter the first number: 4
Enter the second number: 0
Invalid input: division by zero
Enter the first number: 4
Enter the second number: hello
Invalid input: invalid literal for int() with base 10: 'hello'
Enter the first number:

参考资料

Beginning Python 3rd
The Quick Python Book 3rd Edition