处理异常
异常处理通过 try 语句(详细语法及使用规则见 try 语句)来实现。
通过 try 语句处理程序已经出现或可能出现异常,使代码能够继续执行,否则异常未被处理,程序终止执行。
例如下列代码要求用户输入有效的整数:
while True:
try:
x = int(input("Please enter a number: "))
break
except ValueError:
print("Oops! That was no valid number. Try again...")
Please enter a number: 3.14
Oops! That was no valid number. Try again...
Please enter a number: 314
try 语句的工作原理如下:
-
首先执行 try 子句;
-
如果没有异常发生,则跳过 except 子句并完成 try 语句的执行;
-
如果在执行 try 子句时发生了异常,则跳过该子句中剩下的部分。然后,如果异常的类型和 except 关键字后面的异常匹配,则执行 except 子句,然后继续执行 try 语句之后的代码;
-
如果发生的异常和 except 子句中指定的异常不匹配,则将其传递到外部的 try 语句中;如果没有找到处理程序,则它是一个未处理异常,执行将停止并显示异常信息。
一个 try 语句可能有多个 except 子句,以指定不同异常的处理程序,但最多会执行一个处理程序。
处理程序只处理相应的 try 子句中发生的异常。如果发生的异常和 except 子句中的类是同一个类或者是它的基类,则异常和 except 子句中的类是兼容的。如果首先处理了基类,子类不再被处理(子类也被基类处理了):
class B(Exception):
pass
class C(B):
pass
class D(C):
pass
for cls in [B, C, D]:
try:
raise cls()
except D:
print("D")
except C:
print("C")
except B:
print("B")
B
C
D
class B(Exception):
pass
class C(B):
pass
class D(C):
pass
for cls in [B, C, D]:
try:
raise cls()
except B: # 下列都是 B 的子类,不再被处理
print("B")
except C:
print("C")
except D:
print("D")
B
B
B
最后的 except 子句可以省略异常名,以用作通配符。但请谨慎使用,因为以这种方式很容易掩盖真正的编程错误!它还可用于打印错误消息,然后重新引发异常:
import sys
try:
3/0
except ValueError as v:
print(v)
except:
print("Unexpected error:", sys.exc_info()[0])
raise
Unexpected error: <class 'ZeroDivisionError'>
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
<ipython-input-13-f0b3e840c5ab> in <module>
2
3 try:
----> 4 3/0
5 except ValueError as v:
6 print(v)
ZeroDivisionError: division by zero
可选的 else 子句,在使用时必须放在所有的 except 子句后面。对于在 try 子句不引发异常时必须执行的代码来说很有用:
try:
f = open('../11_built-in_function/test.txt',
'r',encoding='utf-8')
except OSError:
print('cannot open')
else:
print(len(f.readlines()), 'lines')
f.close()
3 lines
异常处理程序不仅处理 try 子句中遇到的异常,还处理 try 子句中调用(即使是间接地)的函数内部发生的异常。
如果 try 子句中有多个可能的异常,只处理最先引发的:
def f():
return 3/0
try:
f() * int('a')
except ValueError as v:
print(v)
except ZeroDivisionError as z:
print(z)
division by zero
抛出异常
raise 语句 允许强制发生指定的异常。
raise NameError('HiThere')
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-1-72c183edb298> in <module>
----> 1 raise NameError('HiThere')
NameError: HiThere
如果你需要确定是否引发了异常但不打算处理它,则可以使用更简单的 raise 语句形式重新引发异常:
try:
raise NameError('HiThere')
except NameError:
print('An exception flew by!')
raise
An exception flew by!
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-2-bf6ef4926f8c> in <module>
1 try:
----> 2 raise NameError('HiThere')
3 except NameError:
4 print('An exception flew by!')
5 raise
NameError: HiThere
自定义异常
可以通过创建新的异常类来自定义代码执行错误引发的异常。许多标准模块定义了它们自己的异常,以报告它们定义的函数中可能出现的错误。
异常通常应该直接或间接地从 Exception 类派生。定义的异常类,可以执行任何其他类可以执行的任何操作,但通常保持简单,只提供一些属性,这
些属性允许处理程序为异常提取有关错误的信息。
在创建可能引发多个不同错误的模块时,通常的做法是为该模块定义的异常创建基类,并为不同错误条件创建特定异常类的子类。大多数异常都定义为名称以 Error
结尾,类似于标准异常的命名:
class Error(Exception):
"""Base class for exceptions in this module."""
pass
class InputError(Error):
"""Exception raised for errors in the input.
Attributes:
expression -- input expression in which the error occurred
message -- explanation of the error
"""
def __init__(self, expression, message):
self.expression = expression
self.message = message
class TransitionError(Error):
"""Raised when an operation attempts a state transition that's not allowed.
Attributes:
previous -- state at beginning of transition
next -- attempted new state
message -- explanation of why the specific transition is not allowed
"""
def __init__(self, previous, next, message):
self.previous = previous
self.next = next
self.message = message
raise InputError(3/1,'分母不能为 1')
---------------------------------------------------------------------------
InputError Traceback (most recent call last)
<ipython-input-2-d0c6938bc1c4> in <module>
29 self.message = message
30
---> 31 raise InputError(3/1,'分母不能为 1')
InputError: (3.0, '分母不能为 1')
finally 清理操作
try 语句有另一个可选子句 finally,用于定义必须在所有情况下执行的清理操作(详见 try 语句)。
例如,在 finally 子句中关闭打开的文件:
def read_file():
try:
f = open('../11_built-in_function/test.txt',
encoding='utf-8')
return f.read()
except OSError:
print('不能打开')
finally:
print('执行清理操作')
f.close()
read_file() # 先执行关闭,在执行返回
执行清理操作
'xue.cn\n\n自学是门手艺'
如果 finally 子句中引发了新的异常,清理操作本身无效,则达不到清理目的:
def read_file():
try:
f = open('../11_built-in_function/test.txt',
encoding='utf-8')
return f.read()
except OSError:
print('不能打开')
finally:
print(执行清理操作) # 清理操作引发异常
f.close()
read_file()
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-13-af7cdb6d88f0> in <module>
10 f.close()
11
---> 12 read_file() # 先执行关闭,在执行返回
<ipython-input-13-af7cdb6d88f0> in read_file()
7 print('不能打开')
8 finally:
----> 9 print(执行清理操作) # 清理操作引发异常
10 f.close()
11
NameError: name '执行清理操作' is not defined
某些对象(例如文件对象)定义了在不再需要该对象时,要执行的标准清理操作,无论使用该对象的操作是成功还是失败,清理操作都会被执行。此时使用 with 语句允许像文件这样的对象能够以一种确保它们得到及时和正确的清理的方式使用。
with 语句相当于将引发异常情况下的清理操作放到了 except 子句中,正常情况下的清理操作放到了 finally 子句中。详情见 with 语句。
def read_file():
with open('../11_built-in_function/test.txt',
encoding='utf-8') as f:
return f.read()
read_file()
'xue.cn\n\n自学是门手艺'