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都會執行