程式執行時發生的異常狀況稱為『例外(exception)』。程式錯誤可分以下幾類:
語法錯誤
邏輯錯誤
執行時期異常狀況:例如程式進行存檔時,硬碟空間不足。或程式需下載網路資料,但電腦卻無法連網等。
忽略此錯誤
所有函數皆傳回成功/失敗的狀況–LBYL(Look Before You Leap,三思而後行)
EX:
例外處理–EAPL(Easier to Ask Forgiveness than Permission,取得事後寬恕比事先得到許可要更容易)
上述範例中,有太多重複的程式碼:每次嘗試寫入檔案時檢查錯誤,並在檢查到錯誤時將錯誤狀態回傳。
而硬碟空間錯誤只有在最上層save_to_file()處理,故大多數的錯誤處理其實只是將錯誤回傳至上一層,並一層一層往上傳遞。
例外處理:可避免上述自行建立的傳遞管道。並寫出類似以下的程式碼:
產生例外的行為稱為引發(raise)或是拋出(throw)例外。
取得例外的行為稱為捕獲(catch)例外。
處理例外的程式碼稱為例外處理程式碼(exception-handling code)或是例外處理程序(exception handler)。
Python與大多數具有例外處理機制的程式語言一樣,為不同類型的問題定義相對應的例外。
發生的事件不同,就會引發不同類型的例外。
例外處理程序則可設定只處理特定類型的例外。
Python的例外是以『階層式』架構呈現。
每種例外類型都是Python的類別,它繼承自上述階層架構的上一層類別。
大多數的例外都是繼承自Exception,故建議使用者自定的例外也繼承Exception:因為Ctrl+C可中斷try區塊中的程式碼,而不會觸發例外處理。
alist = [1, 2, 3]
# element = alist[7]
# IndexError: list index out of range
raise敘述來引發,其基本語法如下:raise 例外類型(參數) ## 依照例外類型的不同,參數型別與數量也有所不同
# raise IndexError("Just Testing") ## 自定錯誤訊息
# IndexError: Just Testing
例外處理機制的好處為它們不必導致程式終止,透過定義適當的例外處理程序,則可確保常見的例外情況而不會導致程式執行失敗。
例外處理程序亦可向使用者顯示錯誤訊息或做其他事情,甚至可以解決例外的問題後讓程式繼續運行。
Python採try、except、else關鍵字來捕獲與處理例外,其基本語法如下
try敘述首先會執行程式主體區塊的程式碼,如果執行成功,則執行else區塊的程式碼。
若是有finally子句,最後還會執行finally區塊程式碼。
如果程式主體區塊拋出例外,則會往下依序搜尋except子句,比對被拋出的例外與except預期的類型是否一致。
如果找到匹配的except子句,則會把被拋出的例外設定為as後面的變數,並執行該子句的程式區塊。
若無『as變數』,仍然可以捕獲指定類型的例外,只是不會把拋出的例外設定給任何變數。
最後一個沒有指定類型的except子句可以處理所有類型的例外(可有可無)。但建議不要這樣做。因為所有的錯誤都會被隱藏至這個子句裡面,進而產生某些令人困惑的行為。
如果最後沒有任何except子句可以處理例外,則會將該例外從本層函數往上層拋出,直到有其他try敘述能處理它。
若有finally子句,則無論有沒有發生例外,都會執行finally區塊。即使例外沒有任何except子句能夠處理,也會在finally區塊執行完畢後再往上拋出該例外。
因finally區塊永遠都會被執行,故可在該區塊定義清理用之程式碼。如關閉檔案,斷開資料庫等。
class MyError(Exception):
pass
# raise MyError("發生XXX錯誤")
# MyError: 發生XXX錯誤
try:
raise MyError("發生XXX錯誤")
except MyError as error:
print("狀況:", error)
## 狀況: 發生XXX錯誤
try:
raise MyError("寫入錯誤", "my_filename", 3)
except MyError as error:
#error.args[0], error.args[1], error.args[2]
#print(error)
print("狀況:檔案{1} 發生 {0}\n錯誤碼:{2}".format(error.args[0], error.args[1], error.args[2]))
## 狀況:檔案my_filename 發生 寫入錯誤
## 錯誤碼:3
assert敘述為程式除錯assert 運算式, 參數x = [1, 2, 3]
# assert len(x) > 5, "發生錯誤, len(x)小於等於5"
# AssertionError: 發生錯誤, len(x)小於等於5
assert 運算式等同於以下程式碼:if __debug__:
if not 運算式:
raise AssertionError(參數)
如果程式主體拋出IndexError,因為IndexError是繼承自LookupError的子類別,所以第一個except子句會捕捉到IndexError,導致第二個except子句永遠不會被用到。
故應該兩個except子句順序對調。