例外處理
程式撰寫中, 一定會有一些錯誤, 這些bug除了造成運算結果的不正確, 嚴重的還會引發程式閃退. 比如不小心除以0了. 除以0還是個小事. 但如果將資料寫到硬碟, 寫了一半, 磁碟滿了, 傳送到網路傳到一半斷線了, 那該怎麼辦! 直接閃退死給他看, 還是說偵測到錯誤, 發送解決的請求, 然後再試一次?
白痴也知道會是第二種. 問題是如何去偵測這種不知什麼時候會發作的錯誤呢
try-except就是當今的濟世良方. 請先看一下如下代碼
try: a=10 b=0 c=a/b print("c=%d" %c) exit() except ZeroDivisionError: print("除到0了啦") exit() finally: print("這裏是finally")
上面因為除到0, 所以就會跳到 except中, 列出錯誤訊息後, 執行exit()結束掉. 但很奇怪的是, 都結束了, 為什麼還會印出finally區塊裏面的訊息呢. 這是因為finally非常的頑強, 不論有沒有發生exception, 都會執行finally, 甚至是exit(), 要死, 也要執行完finally才會死
語法
完整語法如下, 比Java多了一個else的區塊
try: pass except ZeroDivisionError: print("zero") except ValueError: print("vaule") else: print("else") finally: print("finally")
Except類別
Python 的例外類別都是繼承了BaseException. 在多個except時, 最前面必需是子類別, 後面才是父類別. 因為父類別的範圍較大, 如果寫在前面的話, 會把所有例外都攔截下來, 那麼後面的子類別就無英雄用武之地了.
Call Stack
大陸翻成調用棧. 不過從這個名詞去看, 不太容易理解, 先以下面的例子作說明.
在math函數中, 發生除以0的錯誤, 但在math並沒有處理, 所以錯誤會丟回上一層的函數. 每一層函數都是一個stack, 所以這種把錯誤交給上一層函數去處理的方式, 稱為Call Stack.
到最上層如果也沒處理的話, 就丟給系統處理, 然後就閃退GG了.
def math(x, y): return x/y def main(): try: r=math(10,0) except ZeroDivisionError: print("Divide by zero") except ValueError: print("VauleError") else: print("No Error") finally: print("This is finally") if __name__=="__main__": main()
log
print(e) 只會印出簡單的說明, 如division by zero. 而logging.exception(e)可以把整個stack的錯誤全都印出來
except ZeroDivisionError as e: print(e) logging.exception(e) 結果 : 直接印 : division by zero ERROR:root:division by zero This is finally Traceback (most recent call last): File "D:/python/oop1.py", line 7, in main r=math(10,0) File "D:/python/oop1.py", line 3, in math return x/y ZeroDivisionError: division by zero
自訂例外
如果要定義自訂例外, 則可以撰寫class, 並繼承相關的例外, 再使用raise 將例外丟出, 如下範例
import logging class MathError(ValueError): pass def math(x, y): if y==0: raise MathError("被除數不能為0啦") return x/y def main(): try: r=math(10,0) except ValueError as e: print(e) if __name__=="__main__": main()
重丟
下面代碼中, 抓到例外後, 可以再繼續丟給上一層, 只要寫raise即可, 不需註明要任的例外, 就會把本身的例外往上丟. 當然也可以寫其他的例外, 轉化成其他的例外物件.
def main(): try: r=math(10,0) except ValueError as e: print(e) raise