9.1 异常
python语言用异常对象来表示异常情况。当遇到错误后,会引发异常。如果异常对象没有处理异常,或未铺获异常,程序就会终止执行,并向用户返回异常信息。
让程序抛出异常的情况有很多,但可以分为两大类:系统自己抛出的异常和主动抛出的异常。如果由于执行了某些代码(例如,分母为0的除法),系统会抛出异常,这种异常是系统自动抛出的。还有一种异常,是由于执行raise语句抛出的异常,这种异常是属于主动抛出的异常。
9.2 主动抛出异常
9.2.1 raise语句
使用raise语句可以直接抛出异常。raise语句可以使用一个类(必须是Exception类或Exception类的子类)或异常对象抛出异常。如果使用类,系统会自动创建类的实例。
raise Exception("z这是自己主动抛出的一个异常")
在python语句中内置了很多异常类,用来描述特定类型的异常,例如,ArithmeticError表示与数值有关的异常。
raise ArithmeticError("这是和数值有关的一个异常")
使用内建的异常是不需要导入任何模块的,不过要使用其他模块中的异常类,就需要导入相应的模块。
from boto.codedeploy.exceptions import InvalidRoleException
raise InvalidRoleException(2,"这是一个和role有关的异常")
一些重要的内建异常类
异常类名 | 描述 |
Exception | 所有类的基类 |
AttributeError | 属性引用或赋值失败时抛出的异常 |
OSError | 当操作系统无法执行任务时抛出的异常 |
IndexError | 在使用序列中不存在的索引时抛出的异常 |
KeyError | 在使用映射中不存在的键值时抛出的异常 |
NameError | 找不到名字(变量)时抛出的异常 |
SyntaxError | 在代码为错误形式时触发 |
TypeError | 在内建操作或函数应用于错误类型的对象时抛出的异常 |
ValueError | 在内建操作或函数应用于正确类型对象,但该对象使用了不合适的值时抛出的异常 |
ZeroDivisionError | 在除法或取模操作的第2个参数值为0时抛出的异常 |
9.2.2 自定义异常类
在很多时候需要自定义异常类。任何一个异常类必须是Exception的子类。最简单的自定义异常类就是一个空的Exception类的子类。
class MyException(Exception):
pass
示例:定义一个曲速引擎(超光速引擎)过载的异常类,当曲速达到10或以上值时就认为是过载,这时就会抛出异常。
class WarpdriveOverloadException(Exception):
pass
warpSpeed = 12
if warpSpeed >= 10:
raise WarpdriveOverloadException("曲速引擎已经过载,请停止或弹出曲速核心,否则飞船将会爆炸")
9.3 捕捉异常
如果异常未被捕捉,系统就会一直将异常传递下去,直到程序由于异常而导致中断。在python语言中使用 try…except 语句进行异常铺获。
9.3.1 try…except 语句的基本用法
try…except 语句用于捕捉代码块的异常。
x = None
while True:
try:
if x == None:
x = int(input("请输入分子:"))
y = int(input("请输入分母:"))
print("x / y = {}".format(x / y))
break;
except :
print("分母不能为0,请重新输入分母!")
知识点:
- try…except 语句是一个代码块,所以try和except后面都要加冒号(:)。
- 如果try语句部分不发生错误,那么程序就会正常执行下去,这是except部分是不会被执行的。如果try和except之间发生了错误,那么错误点后的代码不会被执行了,而会跳到except子句去执行except部分的代码。
- except关键字后边没有指定任何异常类,那么except部分可以捕捉任何的异常。
9.3.2 捕捉多个异常
try:
...
except 异常类1:
...
except 异常类2:
...
...
except 异常类n:
...
except: # 捕捉其他未指定的异常
...
示例:
# 自定义异常类,操作数或计算结果为负数时抛出异常
class NegativeException(Exception):
pass
# 自定义异常类,操作数为0时抛出异常
class ZeroException(Exception):
pass
# 定义加减乘除的类,并抛出指定异常类
class SpecialCalc:
def add(self,x,y):
if x < 0 or y < 0:
raise NegativeException
return x + y
def sub(self,x,y):
if x - y < 0:
raise NegativeException
return x - y
def mul(self,x,y):
if x == 0 or y == 0:
raise ZeroException
return x * y
def div(self,x,y): # 会铺获内建的分母不为0的异常类
return x / y
# 捕捉多种异常情况
while True:
try:
calc = SpecialCalc()
expr = input("请输入要计算的表达式,例如,add(1,2):")
if expr == ":exit":
break;
result = eval('calc.' + expr)
print("计算结果:{:.2f}".format(result))
except NegativeException:
print("******自定义:负数异常******")
except ZeroException:
print("******自定义:操作数为0异常******")
except ZeroDivisionError:
print("******内建:分母不能为0******")
except:
print("******其他异常******")
9.3.3 用同一个代码块处理多个异常
虽然代码块可能抛出多个异常,但有时候多个异常的处理程序可以是一个,在这种情况下,如果用多个except子句捕捉这些异常,就需要在每一个except子句中使用同一段代码处理这些异常。
try:
...
except(异常类1,异常类2,...,异常类n):
...
示例:
class CustomException1(Exception):
pass
class CustomException2(Exception):
pass
class CustomException3(Exception):
pass
import random
def raiseException():
n = random.randint(1,3)
print("抛出CustomException{}异常".format(n))
if n == 1:
raise CustomException1
elif n == 2:
raise CustomException2
else:
raise CustomException3
# 用一个代码块捕捉多个异常
try:
raiseException()
except (CustomException1,CustomException2,CustomException3):
print("******执行异常处理程序******")
9.3.4 捕捉对象
为了更进一步体现异常的差异性,需要为异常类执行一个变量,也可以称为异常对象。其实raise语句抛出的异常类最终也是被创建了异常对象后才抛出的。也就是说,except子句捕捉到的都是异常对象,这里只是给这些异常对象一个名字而已。
为异常对象指定名字需要用as关键字。
try:
...
except 异常类 as ufo:
...
except (异常类1,异常类2,...,异常类n) as ufo110:
...
示例:
class NegativeException(Exception):
pass
class ZeroException(Exception):
pass
class SpecialCalc:
def add(self,x,y):
if x < 0 or y < 0:
raise NegativeException("x和y都不能小于0")
return x + y
def sub(self,x,y):
if x - y < 0:
raise NegativeException("x与y的差值不能小于0")
return x - y
def mul(self,x,y):
if x == 0 or y == 0:
raise ZeroException("x和y都不能等于0")
return x * y
def div(self,x,y):
return x / y
while True:
try:
calc = SpecialCalc()
expr = input("请输入要计算的表达式,例如,add(1,2):")
if expr == ":exit":
break;
result = eval('calc.' + expr)
print("计算结果:{:.2f}".format(result))
except (NegativeException,ZeroException) as e:
print(e) # 输出相应的异常信息
except ZeroDivisionError as e:
print(e) # 输出相应的异常信息
except:
print("******其他异常******")
9.3.5 异常捕捉中的else子句
try…except语句也有else子句。
try:
...
except:
# 抛出异常时执行这段代码
...
except:
# 正常执行后执行这段代码
...
示例:
while True:
try:
x = int(input('请输入分子:'))
y = int(input('请输入分母:'))
value = x / y
print('x / y is', value)
except Exception as e:
print('不正确的输入:',e)
print('请重新输入')
else:
break
9.3.6 异常捕捉中的finally子句
所有需要最后收尾的代码都要放到finally子句中。不管是正常执行,还是抛出异常,最后都会执行finally子句中的代码,所以应该在finally子句中放置关闭资源的代码,如关闭文件、关闭数据库等。
如果使用return语句退出函数,那么首先会执行finally子句中的代码,才会退出函数。因此不必担心finally子句中的代码不会被执行,只要为try语句加上finally子句,并且程序执行流程进入了try语句,finally子句中的代码是一定会被执行的。
try:
...
except:
...
finally: # 无论是否抛出异常,都会执行
...
示例:
def fun1():
try:
print("fun1 正常执行")
finally:
print("fun1 finally")
def fun2():
try:
raise Exception
except:
print("fun2 抛出异常")
finally:
print("fun2 finally")
def fun3():
try:
return 20
finally:
print("fun3 finally")
def fun4():
try:
x = 1/0
except ZeroDivisionError as e:
print(e)
finally:
print("fun4 finally")
try:
del x # 删除x变量时,可能再次出现异常
except Exception as e:
print(e)
9.4 异常、函数与栈跟踪
如果异常被隐藏的很深,而且又不被处理,这种异常是不太好捕捉的,幸亏python解析器可以利用栈进行跟踪。例如,当多个函数调用时,如果最内层的函数抛出一个异常,而且没有得到处理,那么这个异常会一直传播,一直传播到函数顶层,并让程序异常中断。
def fun1():
raise Exception("fun1抛出的异常")
def fun2():
fun1()
def fun3():
fun2()
def fun4():
fun3()
def fun5():
fun4()
fun5()
在fun1中抛出了一个异常,但并未处理,这个异常会一直传播到fun5,最后会导致程序异常中断。在控制台会输出异常栈跟踪信息,很方便的找到根源问题。
9.5 异常的妙用
在合适的地方使用异常,会让程序更简洁,更容易理解。例如,在代码中充斥太多的if语句,会降低代码的可读性,因此利用try语句来取代if语句,并让程序更加健壮。
dict = {'name':'Bill', 'age':40}
try:
print(dict['Age'])
except KeyError as e:
print("异常信息:{}".format(e))
class WifiManager:
def testWifi(self):
print("testWifi")
wifiManager = WifiManager()
try:
wifiManager.testWiFi() # 调用的方法不存在,抛出异常,F大写了
except AttributeError as e:
print("异常信息:{}".format(e))