第九章 异常

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))
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值