python:异常处理

异常

1、一般情况下,Python无法正常处理程序时就会发生一个异常,并终止程序继续运行
     ⑴如:print误拼成Print会怎样?Python会抛出(Raise)一个语法错误,也就是经常说的报错了

2、异常即是一个事件,该事件会在程序执行过程中发生,影响了程序的正常执行
    ⑴异常是Python对象,表示一个错误。当Python脚本发生异常时我们需要捕获处理它,否则程序会终止执行
    ⑵每一个异常都是一些类的实例,这些实例可以被引用,并且可以用很多种方法进行捕捉,使得错误可以被处理,而不是让整个程序失败

3、异常处理就是:对有可能发生异常的代码提前进行一些处理,避免出现异常而导致整个程序停止执行(跳过发生异常的代码,继续往下执行代码)

例1:

with open("C:\\Py_Demo\\Demo\\test.txt") as file:
    print(file.read())

print("前面程序执行发生错误,整个程序停止")


"""
Traceback (most recent call last):
  File "C:/Py_Demo/Demo/Py_Project/zuoye.py", line 1, in 
    with open("C:\\Py_Demo\\Demo\\test.txt") as file:
FileNotFoundError: [Errno 2] No such file or directory: 'C:\\Py_Demo\\Demo\\test.txt'
"""

注:
1、上面代码表示打开一个文件并打印文件内容。但是打开的文件实际是不存在的,因此在执行打开文件操作时,引发了一个"FileNotFoundError"异常,进而导致整个程序停止执行(未执行后面的print()语句)

2、任何没有被处理的错误或异常都会导致python默认处理器的调用,它的作用仅仅是终止程序运行并将错误信息打印出来。前面我们已经见识过了

3、很多时候,我们需要的是:如果程序发生了某些异常,那么就跳过这个异常,继续往下执行代码,而不是整个程序停止运行。要实现这种功能,就需要用到这里的异常处理了

 

 

异常处理

try...except语句

1、通过使用try..except来处理异常状况。一般来说会把通常的语句放在try代码块中,将错误处理器代码放置在except代码块中

2、try...except语句用来检测try语句块中的错误,从而让except语句捕获异常信息并处理

3、如果你不想在异常发生时结束你的程序,只需在try里捕获它,然后在except语句中处理这个错误

4、语法:

try:
<语句>                   #运行的代码(需要检测的语句)

except <名字>:          #发生异常时的操作。名字为对应错误的名称
<语句>

注: 
1、try的工作原理是,当开始一个try语句后,python就在当前程序的上下文中作标记,这样当异常出现时就可以回到做标记的地方

2、try子句先执行,接下来会发生什么依赖于执行时是否出现异常。如果当try后的语句执行时发生异常,python就跳回到try并执行第一个匹配该异常的except子句,每一个try,都必须至少有一个except
    ⑴异常处理完毕,控制流就通过整个try语句(除非在处理异常时又引发新的异常)

3、如果在try后的语句里发生了异常,却没有匹配的except子句,异常将被递交到上层的try,或者到程序的最上层(这样将结束程序,并打印缺省的出错信息)

4、如果在try子句执行时没有发生异常,python将执行else语句后的语句(如果有else的话),然后控制流通过整个try语句

5、当我们认为某些代码可能会出错时,就可以用try来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except语句块,执行完except后,如果有finally语句块,则执行finally语句块

6、在try程序段中,我们放入容易犯错的部分。我们可以跟上except,来说明如果在try部分的语句发生错误时,程序该做的事情。如果没有发生异常,则except部分被跳过

例2:

try:
    with open("C:\\Py_Demo\\Demo\\test.txt") as file:
        print(file.read())
except FileNotFoundError:
    print("打开文件失败:文件可能不存在")

print("不管有没有发生异常,都会继续往下执行")


"""
打开文件失败:文件可能不存在
不管有没有发生异常,都会继续往下执行
"""

注:
1、上面的例子也是打开一个文件失败(文件实际不存在),进而引发了一个FileNotFoundError

2、但是这个例子中我们使用了"异常处理":当try后的语句执行时发生异常,python就跳回到try并执行第一个匹配该异常的except子句。异常处理完毕,控制流就通过整个try语句,继续往下执行代码
    ⑴触发"FileNotFoundError"异常后,执行except语句块,这个例子中我们的处理只是打印一个信息。当然,触发异常后except语句执行什么是我们自己定义的
    ⑵当异常处理完成后(执行完except语句块后),程序继续往下执行:就没有出现因为异常而导致程序整体停止执行的情况

例2_1:

try:
    with open("C:\\Py_Demo\\Demo\\test.txt") as file:
        print(file.read())
except KeyError:
    print("打开文件失败:文件可能不存在")

print("不管有没有发生异常,都会继续往下执行")

"""
Traceback (most recent call last):
  File "C:/Py_Demo/Demo/Py_Project/zuoye.py", line 2, in 
    with open("C:\\Py_Demo\\Demo\\test.txt") as file:
FileNotFoundError: [Errno 2] No such file or directory: 'C:\\Py_Demo\\Demo\\test.txt'
"""

注:
1、这个例子中我们也进行了异常处理,但是为什么程序还是报错并终止执行了?异常处理语句并没有起到任何作用

2、通过报错信息可以看到,实际触发的异常是"FileNotFoundError",但是我们在try...except语句中捕获的异常是"KeyError"。也就是实际上我们并没有真正的捕获到正确的异常,因此异常处理语句没有起到作用

3、所以:在捕获异常时,一定要捕获到正确的异常

例3:

dict = {"A":1,"B":2,"C":3}

def func(element1,element2):
    try:
        value1 = dict[element1]
        value2 = dict[element2]
        info = {'code': value1, 'value': value2}
        return info

    except KeyError:
        print("获取值失败:执行的元素在字典中不存在,返回默认值")
        info = {'code': None, 'value': None}
        return info

info = func("A","D")
print(info)

info1 = func("A","C")
print(info1)

"""
获取值失败:执行的元素在字典中不存在,返回默认值
{'code': None, 'value': None}

{'code': 1, 'value': 3}
"""

注:
1、Python在根据字典键名来获取对应的值时有两种方式:字典["键名"]字典.get("键名")。这两种方法在字典键不存在时返回是不一样的
    ⑴字典["键名"]:字典键名在字典中不存在时,会引发一个"KeyError"的错误
    ⑵字典.get("键名"):字典键名在字典中不存在时,不会报错,而是返回None

2、从这个例子中可以看出:异常处理语句用于在发生异常时,保证程序不会因为异常而停止执行。至于程序中的其他代码可以正常写在except语句中,即使发生了异常也会正常执行(还有后面的finally子句和else子句)

3、有时候,可使用条件语句来达成异常处理实现的目标,但这样编写出来的代码可能不那么自然,可读性也没那么高
    ⑴另一方面,有些任务使用if/else完成时看似很自然,但实际上使用try/except来完成要好得多。因此,应该尽量养成写try/except的习惯

 

异常中的else

1、在try/except语句中还可以加入else语句,表示:当没有异常发生时,else中的语句将会被执行

2、语法:

try:
<语句>        #运行别的代码

except <名字>:
<语句>        #如果在try部份引发了'name1'异常

else:
<语句>        #如果没有异常发生

例4:

def mult_exception(x,y):
    try:

        a = x/y
        print(a)
    except ZeroDivisionError:
        print("Error happend")

    else:
        print("我也被执行了")

mult_exception(2,1)
mult_exception(2,0)

"""
2.0
我也被执行了

Error happend
"""

 

finally子句

1、python中的finally子句需要和try子句一起使用,组成try/finally的语句形式,try/finally语句表示:无论发生异常与否都将执行最后的代码(finally语句块)

2、try/finally语句也可以和except、else一起组合使用
    ⑴但要记得else在except之后,finally在except和else之后(这种组合中except的作用为:用于截获可能发生的异常,使得程序不报错而执行except所属语句)

例5:

def mult_exception(x,y):
    try:
        a = x/y
        print(a)
    except ZeroDivisionError:
        print("some bad things is happend:division by zero")

    else:
        print("没有异常时才执行我")

    finally:
        print("不管怎样我都要被执行")

mult_exception(2,1)
mult_exception(2,0)
"""
2.0
没有异常时才执行我
不管怎样我都要被执行

some bad things is happend:division by zero
不管怎样我都要被执行
"""

 

捕获多个异常

1、try...except...语句中至少要有一个except语句块,也可以有多个except语句块

2、存在多个except语句块时,可以捕获多个异常
3、语法:

try:
<语句>        #运行别的代码

except <名字>:
<语句>        #如果在try部份引发了'name1'异常

except <名字>,<数据>:
<语句>         #如果引发了'name2'异常,获得附加的数据

例6:

def mult_exception(x, y):
    try:
        a = x / y

    except ZeroDivisionError:
        print("this is ZeroDivisionError")

    except NameError:
        print("this is NameError")

    print("不管怎样都会执行我")
mult_exception(2, 0)

"""
this is ZeroDivisionError
不管怎样都会执行我
"""

注:上面代码的执行步骤为
1、首先执行try子句(在关键字try和except之间的语句)。如果没有发生异常,则忽略except字句,try子句执行后结束

2、如果在执行try字句过程中发生异常,try字句余下部分就会被忽略,如果异常的类型和except之后的名称相符,对应的except字句就会被执行
    ⑴一个try语句可能包含多个except字句,分别处理不同的异常,但最多只有一个分支会被执行

3、最后执行try之后的代码

 

使用一个块捕捉多个异常

如果需要使用一个块捕捉多个类型异常,可以将他们作为元组列出。使用该方式时,遇到的异常类型是元组中的任意一个,都会走异常流程

例7:

def mult_exception(x,y):
    try:
        a = x/y
        print(a)

    except (ZeroDivisionError,NameError,TypeError):
        print("错误类型为其中一个:ZeroDivisionError,NameError,TypeError")

mult_exception(2,0)

#错误类型为其中一个:ZeroDivisionError,NameError,TypeError

注:
这样做的好处是:假如我们希望多个except字句输出同样的信息,就没有必要在几个except字句中重复输入语句,放到一个异常块中即可

 

 

捕捉对象

如果希望在except子句中访问异常对象本身,也就是看到一个异常对象真正的异常信息,而不是输出自己定义的异常信息,可以使用"as e"的形式,我们称之为捕捉异常

例8:

def mult_exception(x,y):
    try:
        a = x/y    #错误类型: division by zero
        print(a)

    except (ZeroDivisionError,NameError,TypeError) as e:
        print("错误类型:",e)
        a = x + 1
        print(a)

mult_exception(2,0)

"""
错误类型: division by zero
3
"""

注:
1、有多个异常同时存在时,在执行最前面那个后,不会执行后面的

2、在python2中,捕捉对象的写法是"Exception,e",中间使用","分隔,而不是用as,此处的e也可以使用其他字母,用e意义比较明确,取自except的第一个字母

3、这些写法只是说换了一个更标准的方式来打印错误信息(异常对象真正的异常信息),依旧不会终止程序继续往下执行

 

全捕捉

1、在使用try...except语句捕捉异常时,可能会出现某些异常是我们没有考虑到的,这样的话就会出现漏掉异常的情况。另外异常类型非常多,我们也不可能每个都写上去

2、因此就有了全捕捉:不管是什么异常,都会被捕捉到

例9:

def mult_exception(x,y):
    try:

        a = x/y

    except (ZeroDivisionError,NameError,TypeError) as e:
        print(e)

mult_exception(2,"n")

#上面的代码输出结果为:unsupported operand type(s) for /: 'int' and 'str'

注:
由上面例子可以看出即使程序能自动处理很多种异常,但是还是会有部分异常会被遗漏(逃过了try\except的检查)。在这种情况下,与其使用异常捕捉的try\except语句隐藏异常,还不如让程序立即崩溃

例9_1:

def mult_exception(x,y):
    try:
        b = name
        a = x/y

    except:
        print("Error happend")
        print("依旧会继续往下执行代码")
    print("依旧会继续往下执行代码")
mult_exception(2,"nn")

"""
Error happend
依旧会继续往下执行代码
依旧会继续往下执行代码
"""

注:
1、由上面例子可以看出,可以在except子句中忽略所有异常类,从而让程序输出自己定义的异常信息

2、从实用性方面来讲,不建议这么做,因为这样会隐藏所有的的异常,从而不好找到问题。建议使用抛出异常的方式处理

 

抛出异常

1、如果只想知道是否抛出了异常,并不想处理,使用一个简单的raise语句就可以再次把异常抛出。一旦执行了raise语句,raise后面的语句将不能执行

2、用raise语句来引发一个异常。异常/错误对象必须有一个名字,且它们应是Error或Exception类的子类

3、raise关键字后面是抛出是一个通用的异常类型(Exception),一般来说抛出的异常越详细越好,Python在exceptions模块内建了很多的异常类型,通过使用dir函数来查看exceptions中的异常类型

4、raise关键字后面需要指定你抛出的异常类型为了能够捕获异常,"except"语句必须有用相同的异常来抛出类对象或者字符串

5、raise语法格式如下:

raise [Exception [, args [, traceback]]]

注:
1、语句中 Exception 是异常的类型(例如,NameError)参数标准异常中任一种,args是自已提供的异常参数。最后一个参数是可选的(在实践中很少使用),如果存在,是跟踪异常对象

2、格式:raise 异常名称(‘异常描述’)

例10:

filename = input('please input file name:')

if filename=='hello':
    raise NameError('input file name error !')

else:
    print(filename)
"""
please input file name:ee
ee

please input file name:hello
Traceback (most recent call last):
  File "C:/Py_Demo/Demo/Py_Project/zuoye.py", line 4, in 
    raise NameError('input file name error !')
NameError: input file name error !
"""

例10_1:未触发异常

def func(s):
    if s is None:
        print("s 是空对象")
        raise NameError("传入的列表为None")#raise关键可以不与try/except一起使用
    print(len(s))

list = []
func(list)

"""
0
"""

例10_2:

def func(s):
    if s is None:
        print("s 是空对象")
        raise NameError("传入的列表为None")#raise关键可以不与try/except一起使用
    print(len(s))

list = None
func(list)

"""
s 是空对象
Traceback (most recent call last):
  File "C:/Py_Demo/Demo/Py_Project/zuoye.py", line 8, in 
    func(list)
  File "C:/Py_Demo/Demo/Py_Project/zuoye.py", line 4, in func
    raise NameError("传入的列表为None")#raise关键可以不与try/except一起使用
NameError: 传入的列表为None
"""

注:
1、可以看到raise关键字用于抛出一个异常:程序中的某个值满足某个条件时,可以通过raise关键字来触发一个异常,使程序终止运行
    ⑴用于触发异常的条件是我们自己定义的、出发错误时的错误信息也是我们自己定义的
    ⑵只是说所使用的异常名字必须是Error或Exception类的子类

2、raise关键可以不与try/except一起使用:只是用于抛出一个异常

3、触发raise关键字定义的异常后,整个程序终止运行,不会继续往下执行

例10_3:

def func(s):
    try:
        if s is None:
            print("s 是空对象")
            raise NameError("传入的列表为None")     # 如果引发NameError异常,后面的代码将不能执行
        print(len(s))                               # 这句不会执行,但是后面的except还是会走到
    except NameError:
        print("空对象没有长度")
        print("程序继续往下执行")
    print("程序继续往下执行")
list = None
func(list)

"""
s 是空对象
空对象没有长度
程序继续往下执行
程序继续往下执行
"""

注:
raise关键也可以与try/except一起使用:在raise关键字出抛出一个异常,如果在except处又捕获了这个异常,那么程序也会继续往下执行

例10_4:

def mye( level ):
    if level < 1:
        raise Exception("Invalid level!")
        # 触发异常后,后面的代码就不会再执行
    else:
        print("等级为:",level)
try:
    mye(0)            # 触发异常
except Exception as e:  #通过except来捕获异常
    print("错误信息为:",e)
else:
    print("无异常发生")

#错误信息为: Invalid level!

 

 

传递异常

当你捕获到异常之后又希望再次的触发异常只需要使用不带任何参数的raise关键字

例11:

#在阅读开源代码时,经常能看到,单独一个raise的使用,比如:
try:
    do something
except IOError:
    raise
#这个是把最近一次产生的异常重新抛出来,交给上层 (我已经知道了这个异常并且捕获到了,但是我不处理,而由我的上层调用处理)

例12:

try:
    try:
        raise IOError
    except IOError:
        print("inner exception")
        raise 
except IOError:
    print("outter exception")

"""
inner exception
outter exception
"""


例13:

import os
try:
    openFile = open('notExistsFile.txt','r')
    fileContent = openFile.readlines()
except IOError:
    print('File not Exists')
    if not os.path.exists('notExistsFile.txt'):
        raise
except:
    print('process exception')

"""
File not Exists
Traceback (most recent call last):
  File "C:/Py_Demo/Demo/Py_Project/zuoye.py", line 3, in 
    openFile = open('notExistsFile.txt','r')
FileNotFoundError: [Errno 2] No such file or directory: 'notExistsFile.txt'
"""

注:
1、异常会在捕获之后再次触发同一个异常

2、如果要捕获异常后要重复抛出,请使用raise,后面不要带任何参数或信息

3、except语句不是必须的,finally语句也不是必须的,但是二者必须要有一个,否则就没有try的意义了

 

自定义异常

1、python允许程序员自定义异常,用于描述python中没有涉及的异常情况,自定义异常必须继承Exception类,自定义异常按照命名规范以"Error"结尾,显示地告诉程序员这是异常。自定义异常使用raise语句引发,而且只能通过人工方式触发。

2、因为错误就是类捕获一个错误就是捕获该类的一个实例,因此错误并不是凭空产生的,而是由一些不合理的部分导致的。python的内置函数会抛出很多类型的错误,我们自己编写的函数也可以抛出错误。如果要抛出错误,那么可以根据需要定义一个错误的类,选择好继承关系,然后用raise语句抛出一个错误的实例

例14:

class MyError(Exception):
    def __init__(self):
        pass
    def __str__(self):
        return "这是一个自定义的错误类型"

def MyErrorTset():
    try:
        raise MyError()
    except MyError as error:             #except MyError:
        print("exception info: ",error)  #print("exception info: ",MyError())

MyErrorTset()

#上面代码的输出结果为:exception info:  这是一个自定义的错误类型
#这种写法的结果是:如果多个代码调用这个错误类,得到的错误信息是一样的

例14_1:

class CustomError(Exception):
    def __init__(self,ErrorInfo):
        super().__init__(self)      #初始化父类
        self.errorinfo = ErrorInfo
    def __str__(self):
        return self.errorinfo

if __name__ == '__main__':
    try:
        raise CustomError('客户异常')
    except CustomError as e:
        print(e)

#上面代码的输出结果为:客户异常
#这种写法的结果是:在执行具体某个代码的可以再次自定义错误信息(不同代码调用时,得到的错误信息不一样)

 

异常的函数

如果异常在函数内引发而不被处理,就会传播至函数调用的地方。如果异常在调用的地方还是没有被处理,则会继续传播,一直到达主程序。如果在主程序也没有做异常处理,异常就会被python解释器捕获,输出一个错误信息,然后退出程序

例子15:

def func(x,y):
    return x/int(y)

def func_1(x,y):
    return func(x,y)*10

def main(x,y):
    return func_1(x,y)

main(2,0)

"""
上面代码的输出结果为:
Traceback (most recent call last):
  File "F:/Pycharm_project/Demo/demo/Exerciser_3.py", line 10, in 
    main(2,0)
  File "F:/Pycharm_project/Demo/demo/Exerciser_3.py", line 8, in main
    return func_1(x,y)
  File "F:/Pycharm_project/Demo/demo/Exerciser_3.py", line 5, in func_1
    return func(x,y)*10
  File "F:/Pycharm_project/Demo/demo/Exerciser_3.py", line 2, in func
    return x/int(y)
ZeroDivisionError: division by zero
"""

注:
从上面的例子可以看出,func函数中产生的异常通过函数func_1和main传播,在传播过程中都没有对异常进行处理,因此最后抛出堆栈的异常信息

 

assert语句触发异常

1、assert语句根据后面的表达式的真假来控制程序流。若为True,则往下执行。若为False,则中断程序并调用默认的异常处理器,同时输出指定的提示信息

2、格式:

assert expression,'information'

例16:

def testAssert(x):
    assert x < 1,'Invalid value' #断言失败后,不会继续往下执行
    print(x)

testAssert(1)

"""
Traceback (most recent call last):
  File "C:/Py_Demo/Demo/Py_Project/zuoye.py", line 5, in 
    testAssert(1)
  File "C:/Py_Demo/Demo/Py_Project/zuoye.py", line 2, in testAssert
    assert x < 1,'Invalid value'
AssertionError: Invalid value
"""

例16_1:

def testAssert(x):
    assert x < 1,'Invalid value' #断言失败后,不会继续往下执行
    print(x)

testAssert(0)

"""
0
"""

注:
异常信息是以堆栈的形式被抛出的,因为是从下往上看的。所谓堆栈就是最先被发现的异常信息最后被输出(就像子弹入弹夹和出弹夹一样),也被称作先进后出


 

你可能感兴趣的:(python3)