09.异常

异常

  • 主要内容
    • 1.了解异常的概念
    • 2.掌握如何主动抛出异常(raise语句的用法)
    • 3.掌握自定义异常类
    • 4.掌握如何使用try…except语句捕捉异常
    • 5.掌握try语句的else子句的使用方法
    • 6.掌握try语句的finally子句的使用方法以及注意事项。
    • 7.了解异常栈跟踪
    • 8.了解如何更好地使用异常捕捉

一、什么是异常

  • Python语言用异常对象(exception object)来表示异常情况。当遇到错误后,会发生异常。如果异常对象没有处理异常或未捕获异常,程序就会终止执行,并向用户返回异常信息。通常异常信息会告知出现错误的代码行以及其他有助于定位错误的信息,以便程序员可以快速定位有错误的代码行。
  • 让程序抛出异常的情况很多,但可以分为两大类:系统自己抛出的异常和主动抛出的异常。如果由于执行了某些代码(如分母为0的除法),系统会抛出异常,这种异常是系统自动抛出的(有Python解释器抛出)。还有一种异常,是由于执行raise语句抛出的异常,这种属于主动抛出的异常。这样做的目的主要是系统多种异常都由统一的代码处理,所以将程序从当前的代码行直接跳到了异常处理的代码块。
  • 在捕捉异常时,可以选择用同一个代码块处理所有的异常,也可以每一个异常由一个代码块处理。之所以可以单独对某个异常进行处理,是因为每一个异常就是一个类。抛出异常的过程也就是创建这些类的实例的过程。如果单独捕获某个异常类的实例,那么自然可以用某个代码块单独处理该异常。
x = 1/0
---------------------------------------------------------------------------

ZeroDivisionError                         Traceback (most recent call last)

<ipython-input-1-bfdf2b0f658a> in <module>
----> 1 x = 1/0


ZeroDivisionError: division by zero

二、主动抛出异常

  • 异常可以是系统自动抛出的,也可以由程序员编写代码来主动抛出。以下我们介绍如何通过raise语句自己来抛出异常,以及如何自定义异常类。

1、raise语句

  • 使用raise语句可以直接抛出异常。raise语句可以使用一个类(必须是Exception类或Exception类的子类)或异常对象抛出异常。如果使用类,系统会自动创建类的实例。下面的一些代码会使用内建的Exception异常类抛出异常。
raise Exception 
---------------------------------------------------------------------------

Exception                                 Traceback (most recent call last)

<ipython-input-2-2aee0157c87b> in <module>
----> 1 raise Exception


Exception: 
# 以上异常信息可以看出,除了抛出异常信息的代码文件和代码行外,没有其他有价值的信息,无从得知到底是什么原因引发的异常。
# 因此,最简单的做法就是为异常信息加上一个描述。
raise Exception("这是自己主动抛出的一个异常") # 在raise语句中加了一个Exception对象,并通过类的构造方法传入了异常信息的描述。
---------------------------------------------------------------------------

Exception                                 Traceback (most recent call last)

<ipython-input-5-10da087e4568> in <module>
      1 # 以上异常信息可以看出,除了抛出异常信息的代码文件和代码行外,没有其他有价值的信息,无从得知到底是什么原因引发的异常。
      2 # 因此,最简单的做法就是为异常信息加上一个描述。
----> 3 raise Exception("这是自己主动抛出的一个异常") # 在raise语句中加了一个Exception对象,并通过类的构造方法传入了异常信息的描述。


Exception: 这是自己主动抛出的一个异常
  • 在Python语句中内置了很多异常类,用来描述特定类型的异常,如ArithmeticError表示与数值有关的异常。尽管ArithmeticError类没有强迫我们必须用它来表示与数值有关的异常,但使用有意义的异常类是一个好习惯,大大加强程序的可读性。
raise ArithmeticError("这是一个和数值有关的异常")
---------------------------------------------------------------------------

ArithmeticError                           Traceback (most recent call last)

<ipython-input-6-2b0a7851c5c4> in <module>
----> 1 raise ArithmeticError("这是一个和数值有关的异常")


ArithmeticError: 这是一个和数值有关的异常
  • 使用内建的异常类是不需要导入任何模块的,不过要使用其他模块中的异常类,就需要导入相应的模块。下面的代码抛出了一个InvalidRoleException异常,该类通常表示与Role相关的异常。InvalidRoleException类的构造方法有两个参数,第一个参数需要传入一个数值,表示状态;第二个参数需要传入一个字符串,表示抛出异常的原因。
from boto.codedeploy.exceptions import InvalidRoleException
raise InvalidRoleException(2,"这是一个和Role有关的异常")
---------------------------------------------------------------------------

InvalidRoleException                      Traceback (most recent call last)

<ipython-input-12-352a7367ae64> in <module>
      1 from boto.codedeploy.exceptions import InvalidRoleException
----> 2 raise InvalidRoleException(2,"这是一个和Role有关的异常")


InvalidRoleException: InvalidRoleException: 2 这是一个和Role有关的异常
  • 一些重要的内建异常类
    • 1.Exception:所有异常的基类
    • 2.AttributeError:属性引用或赋值失败时抛出的异常
    • 3.OSError:当操作系统无法执行任务时抛出的异常
    • 4.IndexError:在使用序列中不存在的索引时抛出的异常
    • 5.KeyError:在使用映射中不存在的键值时抛出的异常
    • 6.NameError:在找不到名字(变量)时抛出的异常
    • 7.SyntaxError:在代码为错误形式时触发
    • 8.TypeError:在内建操作或函数应用于错误类型的对象时抛出的异常
    • 9.ValueError:在内建操作或函数应用于正确类型的对象,但该对象使用了不合适的值时抛出的异常
    • 10.ZeroDivisionError:在除法或者取模操作的第2个参数值为0时抛出的异常

2、自定义异常类

  • 在很多时候需要自定义异常类。任何一个异常类必须是Exception的子类。最简单的自定义异常类就是一个空的Exception类的子类。
  • 其实在自定义异常类中可以做更多的工作,如为异常类的构造方法添加更多的参数,本节只是介绍一个最简单的自定义异常类。
class MyException(Exception):
    pass

例:下面用一个科幻点的例子来演示如何自定义异常类,以及如何抛出自定义异常。
本例会定义一个曲速引擎(超光速引擎)过载的异常类,当曲速达到10或以上值时就认为是过载,这时会抛出异常。

# 定义曲速引擎过载的异常类
class WarpdriveOverloadException(Exception):
    pass

# 当前的曲速值
warpSpeed = 12
# 当曲速为10或以上值时认为是曲速引擎过载,应该抛出异常
if warpSpeed >= 10:
    # 抛出自定义异常
    raise WarpdriveOverloadException("曲速引擎已经过载,请停止或弹出曲速核心,否则飞船将会爆炸")
---------------------------------------------------------------------------

WarpdriveOverloadException                Traceback (most recent call last)

<ipython-input-4-95b858255855> in <module>
      8 if warpSpeed >= 10:
      9     # 抛出自定义异常
---> 10     raise WarpdriveOverloadException("曲速引擎已经过载,请停止或弹出曲速核心,否则飞船将会爆炸")


WarpdriveOverloadException: 曲速引擎已经过载,请停止或弹出曲速核心,否则飞船将会爆炸

三、捕捉异常

  • 如果异常未捕捉,系统就会一直将异常传递下去,直到程序由于异常而导致中断。为了尽可能避免出现这种程序异常中断的情况,需要对“危险”的代码段进行异常捕捉。在Python语言中,使用try…except语句进行异常捕获。

1、try…except语句的基本用法

  • try…except语句用于捕捉代码块的异常。在使用try…except语句之前,先看一看不使用该语句的情况。
x = int(input("请输入分子:"))
y = int(input("请输入分母:"))
print("x/y = {}".format(x/y))
请输入分子:20
请输入分母:0



---------------------------------------------------------------------------

ZeroDivisionError                         Traceback (most recent call last)

<ipython-input-6-368372bb1da0> in <module>
      1 x = int(input("请输入分子:"))
      2 y = int(input("请输入分母:"))
----> 3 print("x/y = {}".format(x/y))


ZeroDivisionError: division by zero

执行上面的代码后,分子输入任意的数值,分母输入0,会抛出异常,从而导致程序崩溃,也就是说,本来正在执行第3条语句(print函数),但由于x/y中的y变量是0,所以直接抛出了异常,因此,第3条语句后面的所有语句都不会被执行。由于用户的输入是不可控的,所以当采集用户输入的数据时,应该使用try…except语句对相关代码进行捕捉,有备无患。

例:本例通过try…except语句捕捉用户输入可能造成的异常,如果用户输入了异常数据,会提示用户,并要求重新输入数据。

# 先定义一个x变量,但x变量中没有值(为None)
x = None
while True:
    try:
        # 如果x已经有了值,表示已经捕捉了异常,那么再次输入数据时,就不需要输入x的值了
        if x == None:
            x = int(input("请输入分子:"))
        y = int(input("请输入分母:"))
        print("x/y = {}".format(x/y))
        break;   # 如果分子和分母都正常,那么退出循环
    except:
        print("分母不能为0,请重新输入分母!") # 只有发生异常时,才会执行这行代码
        
请输入分子:520
请输入分母:0
分母不能为0,请重新输入分母!
请输入分母:5
x/y = 104.0

从以上例子可以了解关于try…except语句的如下几方面内容:

  • try…except语句是一个代码块,所以try和except后面都要加冒号(:)。
  • try和except之间是正常执行的语句,如果这些代码不发生错误,那么就会正常执行下去,这时except部分的代码是不会执行的。如果try和except之间的代码发生了错误,那么错误点后面的代码都不会被执行了,而会跳到except子句去执行except代码块中的代码。
  • 如果except关键字后面没有指定任何异常类,那么except部分可以捕捉任何的异常类。

2、捕捉多个异常

  • 我们并不能预估一个代码块到底会不会抛出异常,以及抛出多少种异常。所以需要使用try…except语句捕捉尽可能多的异常,因此,except子句包含任意多个。不过程序员并不能准确估计一个代码块抛出的异常种类,所以使用具体异常类来捕捉异常,有可能会遗漏某个异常,在这种情况下,当抛出这个被遗漏的异常后,程序还是会崩溃,所以比较保险的做法是最后一个except子句不使用任何异常类,这样就会捕捉其他所有未指定的异常。
""""""
try:
    ...
except 异常类1:
    ...
except 异常类2:
    ...
    ...
except 异常类n:
    ...
except:     # 捕捉其他未指定的异常  
    ...
""""""    
''
  • 例:
    本例通过SpecialCalc类的三个方法(add、sub和mul)和raise语句抛出了两个自定义异常(NegativeException和ZeroException),div方法可能会抛出内建的ZeroDivisionError异常。这三个异常分别通过三个except子句捕捉。最后使用except子句捕捉其他未指定的异常。本例的核心逻辑代码在while循环中,通过Console输入表达式(如add(4,2)),动态调用SpecialCalc类的相应方法,不管是抛出异常,还是正常调用,都会重新要求输入新的表达式,直到输入“:exit”命令退出while循环。
# 自定义异常类,表示操作数或计算结果为负数时抛出异常
class NegativeException(Exception):
    pass
# 自定义异常类,表示操作数为0时抛出的异常
class ZeroException(Exception):
    pass

class SpecialCalc:
    def add(self,x,y):
        # 当x和y至少有一个小于0时抛出NegativeException异常
        if x < 0 or y < 0:
            raise NegativeException
        return x + y
    def sub(self,x,y):
        # 当x和y的差值是负数时抛出NegativeException异常
        if x - y < 0:
            raise NegativeException
        return x - y
    def mul(self,x,y):
        # 当x和y至少有一个为0时抛出ZeroException异常
        if x == 0 or y == 0:
            raise ZeroException
        return x * y
    def div(self,x,y):
        return x / y
    
while True:
    try:
        # 创建SpecialCalc类的实例
        calc = SpecialCalc()
        # 从Console输入表达式
        expr = input("请输入要计算的表达式,例如,add(1,2): ")
        # 当输入“:exit”时推出while循环
        if expr == ":exit":
            break;
        # 使用eval函数动态执行输入的表达式,前面需要加上“calc.”前缀,因为这些方法都属于SpecialCalc类
        result = eval('calc.' + expr)
        # 在控制台输出计算结果,保留小数点后两位
        print("计算结果:{:.2f}".format(result))
    except NegativeException:
        print("******负数异常********")
    except ZeroException:
        print("******操作数为0异常****")
    except ZeroDivisionError:
        print("******分母不能为0******")
    except:
        print("******其他异常*******")
        
请输入要计算的表达式,例如,add(1,2): add(5,520)
计算结果:525.00
请输入要计算的表达式,例如,add(1,2): add(-5,520)
******负数异常********
请输入要计算的表达式,例如,add(1,2): sub(18,8)
计算结果:10.00
请输入要计算的表达式,例如,add(1,2): sub(8,18)
******负数异常********
请输入要计算的表达式,例如,add(1,2): mul(666,0)
******操作数为0异常****
请输入要计算的表达式,例如,add(1,2): div(520,0)
******分母不能为0******
请输入要计算的表达式,例如,add(1,2): div(520,1314)
计算结果:0.40
请输入要计算的表达式,例如,add(1,2): love
******其他异常*******
请输入要计算的表达式,例如,add(1,2): exit
******其他异常*******
请输入要计算的表达式,例如,add(1,2): :exit

在输入表达式的过程中,最后输入一个“love”,并不是正确的表达式,而且抛出的异常并没有使用except子句明确指定,因此,最后的except子句会捕捉这个异常。

3、用同一个代码块处理多个异常

  • 虽然代码块可能抛出多个异常,但有时多个异常的处理程序可以是一个,在这种情况下,如果用多个except子句捕捉这些异常,就需要在每一个except子句中使用同一段代码处理这些异常。为了解决这个问题,except子句允许指定多个异常,这样指定后,同一个except子句就可以捕捉多个异常了。

‘’’’’’
try:

except(异常类1,异常类2,异常类3,…,异常类n):

‘’’’’’

例:本例定义了一个raiseException函数,用于随机抛出三个自定义异常,然后用同一个except子句捕捉这三个异常。

# 第1个自定义异常类
class CustomException1(Exception):
    pass
# 第2个自定义异常类
class CustomException2(Exception):
    pass
# 第3个自定义异常类
class CustomException3(Exception):
    pass
# 导入random模块
import random

# 随机抛出前面3个自定义异常
def raiseException():
    n = random.randint(1,3)  # 随机参数1到3的随机整数
    print("抛出CustomeException{}异常".format(n))
    if n == 1:
        raise CustomException1
    elif n == 2:
        raise CustomException2
    else:
        raise CustomException3
        
try:
    raiseException()      # 随机抛出3个异常
# 使用except子句同时捕捉这3个异常
except(CustomException1,CustomException2,CustomException3):
    print("*****执行异常处理程序*****")
    
抛出CustomeException1异常
*****执行异常处理程序*****

4、捕捉对象

  • 使用except子句捕捉了多个异常,但都是根据异常类来输出异常信息的。例如,如果抛出的是NegativeExceptin类,就会输出“负数异常”信息。可能有多处代码都抛出了同一个NegativeException类,尽管异常类似,但会有细微的差别。为了更进一步体现异常的差异性需要为异常类执行一个变量,也可以称为异常对象。其实raise语句抛出的异常类最终也是被创建了异常对象后才抛出的。也就是说,except子句捕捉到的都是异常对象,这里只是给这些异常对象一个名字而已。
""""""
try:
  ...
except 异常类 as e:
  ...
except (异常类1,异常2,...,异常类n) as e:
  ...
""""""
''

如果使用print函数输出e,会将通过构造方法参数传给异常对象的异常信息输出到Console。e就是这些输出的异常信息,根据异常信息就可以更清楚地了解到底是什么引发的异常。

  • 例:用同一个except子句捕捉多个异常,并为这些异常指定一个异常对象变量,当输出异常对象时,就会输出相应的异常信息。
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))
    # 同时捕捉NegativeException和ZeroException异常,并为其指定一个异常对象变量e
    except (NegativeException,ZeroException) as e:
        # 输出相应的异常信息
        print(e)
    # 捕捉ZeroDivisionError异常
    except ZeroDivisionError as e:
        print(e)
    except:
        print("******其他异常********")
        
        
请输入要计算的表达式,例如,add(1,2): add(1,0)
计算结果:1.00
请输入要计算的表达式,例如,add(1,2):add(-1,4)
x和y都不能小于0
请输入要计算的表达式,例如,add(1,2):sub(3,5)
x与y的差值不能小于0
请输入要计算的表达式,例如,add(1,2):sub(9,4)
计算结果:5.00
请输入要计算的表达式,例如,add(1,2):mul(0,4)
x和y都不能等于0
请输入要计算的表达式,例如,add(1,2):div(8,0)
division by zero
请输入要计算的表达式,例如,add(1,2)::exit

5、异常捕捉中的else子句

  • 与循环语句类似,try…except语句也有else子句。与except子句正好相反,except子句中的代码会在try和except之间的代码抛出异常时执行,而else子句会在try和except之间的代码正常执行后才执行。可以利用else子句的这个特性控制循环体的执行,如果没有任何异常抛出,那么循环体就结束,否则一直处于循环状态。
""""""
try:
    ...
except:
    # 抛出异常时执行这段代码
    ...
else:
    # 正常执行后执行这段代码
    ...
""""""   
''

例:本例通过while循环控制输入正确的数值(x和y),并计算x/y的值。如果输入错误的x和y的值,那么就会抛出异常,并输出相应的异常信息。如果输出正确,会执行else子句中的break语句退出循环。

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       # 没有抛出任何异常,直接退出while循环
请输入分子:30
请输入分母:0
不正确的输入: division by zero
请重新输入
请输入分子:1314
请输入分母:520
x / y is 2.526923076923077

对于本例来说,将break语句放到try和except之间的代码块的最后也可以,只是为了将正确执行后要执行的代码分离出来,需要将这些代码放到else子句的代码块中。

6、异常捕捉中的finally子句

  • 捕捉异常语句的最后一个子句是finally。从这个子句的名字基本可以断定是做什么用的。所有需要最后收尾的代码都要放到finally子句中。不管是正常执行,还是抛出异常,最后都会执行finally子句中代码,所以应该在finally子句中放置关闭资源的代码,如关闭文件、关闭数据库等。
  • 如果使用return语句退出函数,那么会首先执行finally子句中的代码,才会退出函数。因此并不用担心finally子句中的代码不会被执行,只要try语句加上了finally子句,并且程序执行流程进入了try语句,finally子句中的代码是一定会执行的。
try:
    ...
except:
    ...
finally:  # 无论是否抛出异常,都会执行finally子句中的代码
    ...

例:本例演示了finally子句在各种场景下的执行情况。

# 未抛出异常时执行finally子句中的代码
def fun1():
    try:
        print("fun1 正常执行")
    finally:
        print("fun1 finally")
# 抛出异常时执行finally子句中的代码
def fun2():
    try:
        raise Exception
    except:
        print("fun2 抛出异常")
    finally:
        print("fun2 finally")
# 用return语句退出函数之前执行finally子句中的代码
def fun3():
    try:
        return 20
    finally:
        print("fun3 finally")
# 抛出异常时执行finally子句中的代码,但在finally子句中执行del x操作,再一次抛出了异常
def fun4():
    try:
        x = 1/0
    except ZeroDivisionError as e:
        print(e)
    finally:
        print("fun4 finally")
        del x
fun1()
fun2()
print(fun3())
fun4()
fun1 正常执行
fun1 finally
fun2 抛出异常
fun2 finally
fun3 finally
20
division by zero
fun4 finally



---------------------------------------------------------------------------

UnboundLocalError                         Traceback (most recent call last)

<ipython-input-8-b7eafc538f57> in <module>
     31 fun2()
     32 print(fun3())
---> 33 fun4()


<ipython-input-8-b7eafc538f57> in fun4()
     27     finally:
     28         print("fun4 finally")
---> 29         del x
     30 fun1()
     31 fun2()


UnboundLocalError: local variable 'x' referenced before assignment

从上面的代码可以看到,当在fun3函数中通过return语句退出函数时,会首先执行finally子句中的代码,然后再退出函数。在fun4函数中,尽管finally子句中的代码正常执行了,但在finally子句中试图通过del语句删除x变量,但由于x变量在创建之初由于抛出异常(分母为0),并未创建成功,所以x变量其实并不存在,因此,在使用del语句删除一个并不存在的变量时会抛出异常,而且这次是在finally子句中抛出异常,而且并没有其他try语句捕捉这个异常,所以这个异常将直接导致程序崩溃。因此,在finally子句中,应该尽可能避免执行容易抛出异常的语句,如果非要执行这类语句,建议再次加上try语句。

try:
    x = 1/0
except ZeroDivisionError as e:
    print(e)
finally:
    print("fun4 finally")
    try:
        del x
    except Exception as e:
        print(e)
division by zero
fun4 finally
name 'x' is not defined

4、异常、函数与栈跟踪

  • 如果异常被隐藏的很深,而且又不被处理,这种异常是不太好捕捉的,幸亏Python解析器可以利用栈进行跟踪。例如,当多个函数进行调用时,如果最里层的函数抛出一个异常,而且没有得到处理,那么这个异常会一直进行传播,直到传播到顶层函数,并让程序异常中断。

例:本例定义了5个函数:fun1~fun5,后面的函数会调用前面的函数,如fun2会调用fun1,fun3会调用fun2,以此类推。在fun1中抛出了一个异常,但并未处理,这个异常会一直传播到fun5,最后会导致程序异常中断。Console中会输出异常栈跟踪信息。

def fun1():
    raise Exception("fun1抛出的异常")
def fun2():
    fun1()
def fun3():
    fun2()
def fun4():
    fun3()
def fun5():
    fun4()
fun5()     # 调用fun5函数会抛出异常

---------------------------------------------------------------------------

Exception                                 Traceback (most recent call last)

<ipython-input-1-a7df42983873> in <module>
      9 def fun5():
     10     fun4()
---> 11 fun5()     # 调用fun5函数会抛出异常


<ipython-input-1-a7df42983873> in fun5()
      8     fun3()
      9 def fun5():
---> 10     fun4()
     11 fun5()     # 调用fun5函数会抛出异常


<ipython-input-1-a7df42983873> in fun4()
      6     fun2()
      7 def fun4():
----> 8     fun3()
      9 def fun5():
     10     fun4()


<ipython-input-1-a7df42983873> in fun3()
      4     fun1()
      5 def fun3():
----> 6     fun2()
      7 def fun4():
      8     fun3()


<ipython-input-1-a7df42983873> in fun2()
      2     raise Exception("fun1抛出的异常")
      3 def fun2():
----> 4     fun1()
      5 def fun3():
      6     fun2()


<ipython-input-1-a7df42983873> in fun1()
      1 def fun1():
----> 2     raise Exception("fun1抛出的异常")
      3 def fun2():
      4     fun1()
      5 def fun3():


Exception: fun1抛出的异常

从以上程序执行结果来看,Python解释器会将异常发生的源头以及其传播的路径都显示出来,这样就可以很容易地按图索骥,找到异常发生的根源。

5、异常的妙用

  • 在合适的地方使用异常,会让程序更加简单,更容易理解。例如,通过key从字典中获取value时,为了防止由于key不存在而导致的异常,可以利用条件语句进行判断。
# 下面的代码直接通过key从字典中获取value。
dict = {'name': = 'Bill','age':40}
dict['Age']
  File "<ipython-input-2-434300e16ee2>", line 2
    dict = {'name': = 'Bill','age':40}
                    ^
SyntaxError: invalid syntax

因为字典中的key是大小写敏感的。Age在dict中并不存在。执行代码后,抛出异常。
为了避免抛出的这个异常,可以使用if语句和in操作符进行判断。代码如下:

dict = {'name':'Bill', 'age':40}
# 该条件为False,所以条件语句内的代码不会执行,也就不会抛出异常了
if 'Age' in dict:
    print(dict['Age'])
  • 由于Python是动态语言,所以事先不会判断对象中是否存在某个属性或方法,只有在运行时才会由于属性或方法不存在而抛出异常。
class WifiManager:
    def testWifi(self):
        print("testWifi")
wifiManager = WifiManager()
wifiManager.testWiFi()
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-4-e90350855832> in <module>
      3         print("testWifi")
      4 wifiManager = WifiManager()
----> 5 wifiManager.testWiFi()


AttributeError: 'WifiManager' object has no attribute 'testWiFi'

在上面的代码中,testWiFi方法在WifiManager中并不存在(F的大小写问题),所以调用testWiFi方法会抛出以上的异常。
为了防止抛出类似的异常,需要在访问对象属性或方法之前,使用if语句进行判断。

class WifiManager:
    def testWifi(self):
        print("testWifi")
wifiManager = WifiManager()
# 使用hasattr函数判断testWiFi方法是否属性wifiManager对象
if hasattr(wifiManager,'testWiFi'):
    wifiManager.testWiFi()
  • 为了避免抛出异常,一般都可以使用if语句判断。但是在代码中充斥太多if语句,会降低代码的可读性,因此可以用try语句来取代if语句。

例:本例会使用try语句来替换前面代码中的if语句,这样即使程序抛出异常,也不会产生中断情况。

dict = {'name':'Bill','age':40}
try:
    print(dict['Age'])
except KeyError as e:  # 捕捉key不存在的异常
    print("异常信息:{}".format(e))
class WifiManager:
    def testWifi(self):
        print("testWifi")
        
wifiManager = WifiManager()
try:
    wifiManager.testWiFi()
except AttributeError as e:   #捕捉对象属性(方法也可以看作对象的属性)不存在的异常
    print("异常信息:{}".format(e))    
异常信息:'Age'
异常信息:'WifiManager' object has no attribute 'testWiFi'

小结

  • 本章讲解了Python语言中异常的概念以及如何异常捕捉。不管是系统抛出的异常,还是自己使用raise语句抛出的异常,其实都是异常对象。如果在raise语句后面直接跟异常类,那么raise语句会自动利用该类创建一个异常类。另外,Python语言中的try语句其实有4个关键字:try、except、else和finally。这里只有try是必需的,但except和finally必须至少要有一个,else子句可有可无。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值