1. 程式人生 > >chapter10.1、異常處理

chapter10.1、異常處理

異常處理

異常Exception

錯誤  Error

  邏輯錯誤:演算法寫錯了,

  筆誤:變數名錯誤或者語法錯誤

  函式或類使用錯誤,也屬於邏輯錯誤

  大部分的錯誤都可以避免

異常Exception

  本意就是意外情況,在沒有錯誤的前提下,在某些情況下,會有意外,導致程式無法正常執行下去

  例如open檔案,已存在或已經建立,就是異常,或者訪問網路檔案,斷網了,也是異常,本身就是意外。

  異常不可避免

異常和錯誤

  在高階程式語言中,一般都有錯誤和異常的概念,異常可以被捕獲,錯誤不能被捕捉

  python中錯誤也放在異常中,但是一般其他語言不會,錯誤無法捕獲。

一個健壯得程式,儘可能避免錯誤,儘可能捕獲處理各種異常

 

異常的產生

raise語句,丟擲異常

python直譯器執行時,檢測到異常並引發它,例如除零異常

異常會在異常丟擲的地方中斷執行,如果不捕獲,就會提前結束程式(其實是終止當前執行緒的執行)

raise後什麼都沒有,表示拋除最近得一個被啟用的異常,

raise 後要求放BaseException類的子類或者例項,如果是類,將被無參例項化,無參構造器

 

異常的捕獲

try:

  待捕獲的異常程式碼塊

except [異常型別]:

  異常的處理程式碼塊

try:
    print
('______') c = 1/0 print('!!!!!!') except: print('catch the exception') print('*******')

 

在異常處中斷執行,處理異常,異常後的語句不在執行,只在第一個異常中斷處丟擲異常,其他語句不在執行,轉而執行對應的except的語句,最後執行try...except外的語句。

可以捕獲特定異常

try:
    print('______')
    c = 1/0
    print('!!!!!!')
except ArithmeticError:
    print
('catch the exception') print('*******')

健壯性,優先於程式碼好看

異常類及繼承層次

BaseException 所有內建的異常的基類

SystemExit  丟擲系統異常,退出當前程序,

  sys.exit() 函式引發該異常,異常不捕獲處理,就交給直譯器,直譯器退出

  如果except語句捕獲了該異常,則繼續向後執行,如果沒有捕獲該異常,直譯器直接退出程式

  返回的值非零,表示異常,0為正常

KeyboardInterrupt  對應捕獲使用者中斷行為,以下是Ctrl + C

try:
    import time
    while True:
        time.sleep(2)
        print('!!!!!')
except KeyboardInterrupt:##鍵盤打斷方式是ctrl+c
    print('stop')
print('outer')

直接執行以上檔案,在控制檯測試,

GeneratorExit  捕獲迭代器退出異常

Exception  所有內建的,非系統退出的異常的基類,自定義的異常應該繼承自它

SyntaxError  語法出錯誤,無法捕獲

LookupError  使用對映的鍵或者序列的索引無效時引發的異常的基類IndexError,KeyError

ArithmeticError  所有算數計算引發的異常,除零異常是其子類

 

異常的捕獲

except可以捕獲多個異常

class MyException(BaseException):
    pass

try:
    a = 1/0
    raise MyException  ##自定義的類
    open('aaaa.txt')  ##無法執行
except MyException:
    print('catch the MyException')
except ZeroDivisionError:
    print('1/0')
except Exception:
    print('Exception')

 

捕獲規則

捕獲是從上到下依次比較,如果匹配,則執行匹配的except語句塊

如果被其中一個except捕獲,其他的except語句就不會再次捕獲了

如果沒有任何一個except語句捕獲到這個異常,則該異常向外丟擲

捕獲原則

從小到大,從具體到寬泛

多個異常的接收,會相互干擾,父類放下邊(越具體的型別越往上寫)

 

as子句

被丟擲的異常,是異常的例項,使用as子句獲得該物件

 

class MyException(BaseException):
    def __init__(self,code,message):
        self.code = code
        self.message = message

try:
    raise MyException  ##自定義的類,raise後跟類名,是無參構造例項
except MyException as e:
    print('MyException = {} {}'.format(e.code,e.message))
except Exception as e:
    print('Exception = {}'.format(e))

raise  後跟類名是無參構造例項,而自建的類沒有設定預設值,因此需要2個引數,可以改為

class MyException(BaseException):
    def __init__(self,code,message):
        self.code = code
        self.message = message

try:
    raise MyException(100,'wrong gagaga')   ##自定義的類,raise後跟
except MyException as e:
    print('MyException = {}: {}'.format(e.code,e.message))
except Exception as e:
    print('Exception = {}'.format(e))

 

finally子句

try...finally子句中,最後一定會執行的,不管語句中是否有異常,都要執行finally部分

f = None    ##解決作用域問題
try:
    f = open('test111.txt')
except FileNotFoundError as e:
    print('{} {} {}'.format(e.__class__,e.errno,e.strerror))
finally:
    print('qingli')
    if f:
        f.close()

在finally中,可以作為最後一道關卡,釋放資源,解決異常,當然,解決不了就丟擲給外層。

try:
    f = open('test111.txt')
except Exception as e:
    print('{}'.format(e))
finally:
    print('qingli')
    try:
        f.close()
    except NameError as e:
        print(e)
def foo():
    try:
        return 3
    finally:
        print('finally')
    print('====')
print(foo())

以上函式,雖然return已經執行,得到了3,但是還是會執行finally語句,所以列印了finally才退出

def bar():
    try:
        return 3
    finally:
        return 5
    print("===")
print(bar())

以上函式,先return了3,但是finally執行後,return了5,函式返回,5被壓在棧頂,所以返回5。

理解為:函式的返回值取決於最後一個執行的return語句,而finally則是try...finally中最後執行的語句塊。

 

異常的傳遞

def foo1():
    return 1/0

def foo2():
    print('foo2 start')
    foo1()
    print('foo2 stop')

foo2()

上例中,foo2呼叫了foo1,foo1產生了異常,傳遞到了foo2

異常是向外傳遞的,如果外層沒有處理這個異常,就會繼續向外丟擲

如果內層捕獲了異常,外部就捕獲不到了

異常向外傳遞到執行緒,都沒有處理,會導致異常所在的執行緒崩潰,如果該執行緒是工作執行緒,會導致很大的問題,所以異常要處理,但也不能隨意壓制

def foo2():
    time.sleep(3)
    print('foo start')
    foo()
    print('foo2 stop')

t = threading.Thread(target=foo2)
t.start()

while True:
    time.sleep(1)
    print('All ok')
    if t.is_alive():
        print('alive')
    else:
        print('dead')

異常產生後,執行緒結束。

 

try巢狀

try:
    try:
        ret = 1 / 0
    except KeyError as e:##沒有捕獲異常
        print(e)
    finally:
        print('inner fin')
except:  ##傳遞給了外層
    print('outer catch')
finally:
    print('outer fin')

 

內部捕獲不到異常,會向外傳遞。

但如果內層有finally並且其中有return、break語句,則異常就不會繼續向外丟擲:

def foo():
    try:
        ret = 1 / 0
    except KeyError as e:
        print(e)
    finally:
        print('inner fin ')
        return   #return 會壓制異常,不向外傳遞

try:
    foo()
except:
    print('outer catch')
finally:
    print('outer fin')

 

異常的捕獲時機

立即捕獲

需要立即返回一個明確的結果

def parse_int(s):
    try:
        return int(s)
    except:
        return 0
print(parse_int('s'))

 

邊界捕獲

封裝產生了邊界

比如open函式,使用者使用時,模組內部捕獲了檔案存在或不存在,它如果不返回異常,內部處理了,那我們就無法感知。

如果寫一個類,裡邊呼叫了open,但出現異常,不知道如何處理,就繼續向外丟擲,一般來說最外層也是邊界,必須處理這個異常,否則執行緒退出。

 

else子句

沒有異常發生,就執行else

 

總結:

try:
  <語句>  #執行其他程式碼
except <異常類> : 
  <語句>  #捕獲某種型別的異常
except<異常類> as <變數名>:
  <語句>  #捕獲某種型別的異常,並獲得物件
else:
  <語句>  #沒有異常就執行
finally:
  <語句>  #退出時一定會執行

 

 

try工作原理

1、如果try語句執行時發生異常,搜尋except子句,並執行第一個匹配該異常的子句

2、如果發生異常,卻沒有匹配except子句,那就會交到外層的try,如果外層不處理這個異常,就繼續向外層傳遞,直到到達邊界,再不處理就終止異常所在的執行緒

3、如果沒有發生異常,將執行else子句的語句

4、無論是否發生異常,finally都會執行