Python 的例外處理機制可以讓程式具有極好的容錯性,讓程式更加健壯。當程式執行出現意外情況時,系統會自動生成一個 Error 物件來通知程式,從而實現將“業務實現程式碼”和“錯誤處理程式碼”分離,提供更好的可讀性。
使用try...except捕獲異常
前面章節講過,希望有一個非常強大的“if 塊”,可以表示所有的錯誤情況,讓程式一次處理所有的錯誤,也就是希望將錯誤集中處理。
出於這種考慮,此處試圖把“錯誤處理程式碼”從“業務實現程式碼”中分離出來。將上面最後一段偽碼改為如下偽碼:
if 一切正常:
#業務實現程式碼
else:
alert 輸入不合法
goto retry
上面程式碼中的“if 塊”依然不可表示,因為一切正常是很抽象的,無法轉換為計算機可識別的程式碼。在這種情形下,Python 提出了一種假設,如果程式可以順利完成,那就“一切正常”,把系統的業務實現程式碼放在 try 塊中定義,把所有的例外處理邏輯放在 except 塊中進行處理。
下面是 Python 例外處理機制的語法結構:
try:
#業務實現程式碼
...
except (Error1, Error2, ...) as e:
alert 輸入不合法
goto retry
如果在執行 try 塊裡的業務邏輯程式碼時出現異常,系統自動生成一個異常物件,該異常物件被提交給 Python 直譯器,這個過程被稱為
引發異常。
當 Python 直譯器收到異常物件時,會尋找能處理該異常物件的 except 塊,如果找到合適的 except 塊,則把該異常物件交給該 except 塊處理,這個過程被稱為捕獲異常。如果 Python 直譯器找不到捕獲異常的 except 塊,則執行時環境終止,Python 直譯器也將退出。
不管程式程式碼塊是否處於 try 塊中,甚至包括 except 塊中的程式碼,只要執行該程式碼塊時出現了異常,系統總會自動生成一個 Error 物件。如果程式沒有為這段程式碼定義任何的 except 塊,則 Python 直譯器無法找到處理該異常的 except 塊,程式就在此退出,這就是前面看到的例子程式在遇到異常時退出的情形。
下面使用例外處理機制來改寫前面章節中五子棋遊戲中使用者下棋部分的程式碼:
inputStr = input("請輸入您下棋的坐標,應以x,y的格式:n")
while inputStr != None :
try:
# 將使用者輸入的字串以逗號(,)作為分隔符,分隔成2個字串
x_str, y_str = inputStr.split(sep = ",")
# 如果要下棋的點不為空
if board[int(y_str) - 1][int(x_str) - 1] != "╋":
inputStr = input("您輸入的坐標點已有棋子了,請重新輸入n")
continue
# 把對應的列表元素賦為"●"。
board[int(y_str) - 1][int(x_str) - 1] = "●"
except Exception:
inputStr = input("您輸入的坐標不合法,請重新輸入,下棋坐標應以x,y的格式n")
continue
...
上面程式把處理使用者輸入字串的程式碼都放在 try 塊裡執行,只要使用者輸入的字串不是有效的坐標值(包括字母不能正確解析,沒有逗號不能正確解析,解析出來的坐標引起陣列越界……),系統就將引發一個異常物件,並把這個異常物件交給對應的 except 塊處理。
except 塊的處理方式是向使用者提示坐標不合法,然後使用 continue 忽略本次迴圈剩下的程式碼,開始執行下一次迴圈。這就保證了該五子棋遊戲有足夠的容錯性,即使用者可以隨意輸入,程式不會因為使用者輸入不合法而突然退出,程式會向使用者提示輸入不合法,讓使用者再次輸入。
異常類的繼承體系
當 Python 直譯器接收到異常物件時,如何為該異常物件尋找 except 塊呢?注意上面程式中 except 塊的 except Exception:,這意味著每個 except 塊都是專門用於處理該異常類及其子類的異常範例。
當 Python 直譯器接收到異常物件後,會依次判斷該異常物件是否是 except 塊後的異常類或其子類的範例,如果是,Python 直譯器將呼叫該 except 塊來處理該異常;否則,再次拿該異常物件和下一個 except 塊裡的異常類進行比較。
Python 異常捕獲流程示意圖如圖 1 所示:
圖 1 Python 異常捕獲流程示意圖