零基礎學Python--------第9章 異常處理及程式除錯
第9章 異常處理及程式除錯
9.1 異常概述
在程式執行過程中,經常會遇到各種各樣的錯誤,這些錯誤統稱為“異常”。這些異常有的是由於開發者將關鍵字敲錯導致的,這類錯誤多數產生的是SyntaxError:invalid syntax(無效的語法),這將直接導致程式不能執行。這類異常是顯式的,在開發階段很容易被發現。還有一類是隱式的,通常和使用者的操作有關。
例項01:模擬幼兒園分蘋果
def division(): '''功能:分蘋果''' print("\n=====================分蘋果======================\n") apple = int(input("請輸入蘋果的個數:"))# 輸入蘋果的數量 children = int(input("請輸入來了幾個小朋友:")) result = apple//children# 計算每人分幾個蘋果 remain = apple-result*children# 計算餘下幾個蘋果 if remain>0: print(apple,"個蘋果,平均分給",children,"個小朋友,每人分",result,"個,剩下",remain,"個。") else: print(apple,"個蘋果,平均分給",children,"個小朋友,每人分",result,"個。") if __name__ == '__main__': division()# 呼叫分蘋果的函式
執行程式,當輸入蘋果和小朋友的數量都是10時:
請輸入蘋果的個數:10 請輸入來了幾個小朋友:10 10 個蘋果,平均分給 10 個小朋友,每人分 1 個。
如果在輸入數量時,不小心把小朋友的人數輸成了0:
產生了ZeroDivisionError(除數為0錯誤)的根源在於算術表示式“10/0”中,0作為除數出現,所以正在執行的程式中被中斷(第6行以後,包括第6行的程式碼都不會被執行)。
Python中還有很多異常。
異常 | 描述 |
NameError | 嘗試訪問一個沒有宣告的變數引發的錯誤 |
IndexError | 索引超出序列範圍引發的錯誤 |
IndentationError | 縮排錯誤 |
ValueError | 傳入錯誤 |
KeyError | 請求一個不存在的字典關鍵字引發的錯誤 |
IOError | 輸入輸出錯誤(如要讀取的檔案不存在) |
ImportError | 當import語句無法找到模組或from 無法在模組中找到相應的名稱時引發的錯誤 |
AttributeError | 嘗試訪問未知的物件屬性引發的錯誤 |
TypeError | 型別不合適引發的錯誤 |
MemoryError | 記憶體不足 |
ZeroDivisionError | 除數為0 引發的錯誤 |
9.2 異常處理語句
在程式開發時,有些錯誤並不是每次執行都會出現。
下面將詳細介紹Python中提供的異常處理語句。
9.2.1 try...eccept
在Python中,提供了try...except語句捕獲並處理異常。在使用時,把可能產生異常的程式碼放在try 語句塊中,把處理結果放在except 語句塊中,這樣,當try 語句塊中的程式碼出現錯誤時,就會執行except語句塊中的程式碼,如果try 語句塊中的程式碼沒有錯誤,那麼except 語句塊將不會執行。具體的語法格式如下:
try: block1 except [ExceptionName [as alias]]: block2
引數說明:
- block1:表示可能出現錯誤的程式碼塊。
- ExceptionName [as alias]:可選引數,用於指定要捕獲的異常。其中,ExceptionName表示要捕獲的異常名稱,如果在其右側加上as alias,則表示為當前的異常指定一個別名,通過該別名,可以記錄異常的具體內容。
說明:在使用try...except語句捕獲異常時,如果在except後面不指定異常名稱,則表示捕獲全部異常。
- block2:表示進行異常處理的程式碼塊。在這裡可以輸出固定的提示資訊,也可以通過別名輸出異常的具體內容。
說明:使用try...except語句捕獲異常後,當程式出錯時,輸出錯誤資訊後,程式會繼續執行。
例項02:模擬幼兒園分蘋果(除數不能為0)
對“if __name__ =='__main__':”語句下面的程式碼進行修改,應用try...except語句捕獲執行division()函式可能丟擲的ZeroDivisionError(除數為零)異常,修改後的程式碼如下:
def division(): '''功能:分蘋果''' print("\n=====================分蘋果======================\n") apple = int(input("請輸入蘋果的個數:"))# 輸入蘋果的數量 children = int(input("請輸入來了幾個小朋友:")) result = apple//children# 計算每人分幾個蘋果 remain = apple-result*children# 計算餘下幾個蘋果 if remain>0: print(apple,"個蘋果,平均分給",children,"個小朋友,每人分",result,"個,剩下",remain,"個。") else: print(apple,"個蘋果,平均分給",children,"個小朋友,每人分",result,"個。") if __name__ == '__main__': try:# 捕獲異常 division()# 呼叫分蘋果的函式 except ZeroDivisionError:# 處理異常 print("\n出錯了 ~_~ ——蘋果不能被0個小朋友分!")
執行以上的程式碼,輸入蘋果的數量為10,小朋友的人數為0時,將不再丟擲異常:
目前,我們只處理了除數為0的情況,如果將蘋果和小朋友的數量輸入成小數或者不是數字會是什麼結果呢?
可以看出,程式中要求輸入整數,而實際輸入的是小數,則丟擲ValueError(傳入的值錯誤)異常。要解決該問題,可以在例項02的程式碼中,為try...except語句再新增一個except語句,用於處理丟擲ValueError異常的情況。修改後的程式碼如下:
def division(): '''功能:分蘋果''' print("\n=====================分蘋果======================\n") apple = int(input("請輸入蘋果的個數:"))# 輸入蘋果的數量 children = int(input("請輸入來了幾個小朋友:")) result = apple//children# 計算每人分幾個蘋果 remain = apple-result*children# 計算餘下幾個蘋果 if remain>0: print(apple,"個蘋果,平均分給",children,"個小朋友,每人分",result,"個,剩下",remain,"個。") else: print(apple,"個蘋果,平均分給",children,"個小朋友,每人分",result,"個。") if __name__ == '__main__': try:# 捕獲異常 division()# 呼叫分蘋果的函式 except ZeroDivisionError:# 處理異常 print("\n出錯了 ~_~ ——蘋果不能被0個小朋友分!") except ValueError as e:# 處理ValueError異常 print("輸入錯誤:",e)# 輸出錯誤原因
再次執行程式:
多學兩招:
在捕獲異常時,如果需要同時處理多個異常也可以採用下面的程式碼實現:
try:# 捕獲異常 division()# 呼叫分蘋果的函式 except (ZeroDivisionError,ValueError) as e:# 處理異常 print("輸入錯誤:",e)# 輸出錯誤原因
即在except語句後面使用一對小括號將可能出現的異常名稱括起來,多個異常名稱之間使用逗號分隔。如果想要顯示具體的出錯原因,那麼再加上as指定一個別名。
9.2.2 try...except...else 語句
在Python中國,還有另一種異常處理結構,它是try...except...else語句,也就是在原來try...except語句的基礎上再新增一個else子句,用於指定當try語句塊中沒有發現異常時要執行的語句塊。該語句塊中的內容當try語句中發現異常,將不被執行。例如,例項02進行修改,實現當division()函式被執行後沒有丟擲異常時,輸出文字“分蘋果順利完成...”。修改後的程式碼如下:
def division(): '''功能:分蘋果''' print("\n=====================分蘋果======================\n") apple = int(input("請輸入蘋果的個數:"))# 輸入蘋果的數量 children = int(input("請輸入來了幾個小朋友:")) result = apple//children# 計算每人分幾個蘋果 remain = apple-result*children# 計算餘下幾個蘋果 if remain>0: print(apple,"個蘋果,平均分給",children,"個小朋友,每人分",result,"個,剩下",remain,"個。") else: print(apple,"個蘋果,平均分給",children,"個小朋友,每人分",result,"個。") if __name__ == '__main__': try:# 捕獲異常 division()# 呼叫分蘋果的函式 except ZeroDivisionError:# 處理異常 print("\n出錯了 ~_~ ——蘋果不能被0個小朋友分!") except ValueError as e:# 處理ValueError異常 print("輸入錯誤:",e)# 輸出錯誤原因 else:# 沒有丟擲異常時執行 print("分蘋果順利完成...")
執行以上程式碼:
9.2.3 try...except...finally 語句
完整的異常處理語句應該包含finally 程式碼塊,通常情況下,無論程式中有無異常產生,finally 程式碼塊中的程式碼都會被執行,其語法格式如下:
try: block1 except [ExceptionName [as alias]]: block2 fianlly: block3
對於try...except...finally 語句的理解並不複雜,它只是比try...except 語句多了一個finally 語句,如果程式中有一些在任何情形中都必須執行的程式碼,那麼就可以將它們放在finally 程式碼塊中。
說明:使用except 子句是為了允許處理異常。無論是否引起了異常,使用finally 子句都是可以執行清理程式碼。如果分配了有限的資源(如開啟檔案),則應將釋放這些資源的程式碼放置在finally 程式碼塊中。
例如,在對例項02進行修改,實現當division() 函式在執行時無論是否丟擲異常,都輸出文字“進行了一次分蘋果操作”。修改後的程式碼如下:
def division(): '''功能:分蘋果''' print("\n=====================分蘋果======================\n") apple = int(input("請輸入蘋果的個數:"))# 輸入蘋果的數量 children = int(input("請輸入來了幾個小朋友:")) result = apple//children# 計算每人分幾個蘋果 remain = apple-result*children# 計算餘下幾個蘋果 if remain>0: print(apple,"個蘋果,平均分給",children,"個小朋友,每人分",result,"個,剩下",remain,"個。") else: print(apple,"個蘋果,平均分給",children,"個小朋友,每人分",result,"個。") if __name__ == '__main__': try:# 捕獲異常 division()# 呼叫分蘋果的函式 except ZeroDivisionError:# 處理異常 print("\n出錯了 ~_~ ——蘋果不能被0個小朋友分!") except ValueError as e:# 處理ValueError異常 print("輸入錯誤:",e)# 輸出錯誤原因 else:# 沒有丟擲異常時執行 print("分蘋果順利完成...") finally:# 無論是否丟擲異常都執行 print("進行了一次分蘋果操作。")
執行程式:
至此,已經介紹了異常處理語句的try....except、try...except...else 和 try...except...finally 等形式。
異常處理語句的不同子句的執行關係
9.2.4 使用raise 語句丟擲異常
如果某個函式或方法可能會產生異常,但不想在當前函式或方法中處理這個異常,則可以使用raise 語句在函式或方法中丟擲異常。raise 語句的語法格式如下:
raise [ExceptionName[(reason)]]
其中,ExceptionName[(reason)]為可選引數,用於指定丟擲的異常名稱以及異常資訊的相關描述。如果省略,就會把當前的錯誤原樣丟擲。
說明:ExceptionName[(reason)]引數中的“(reason)”也可以省略,如果省略,則在丟擲異常時,不附帶任何描述資訊。
例如,修改例項02,加入限制蘋果數量必須大於或等於小朋友的數量,從而保證每個小朋友都能至少分到一個蘋果。
例項03:模擬幼兒園分蘋果(每個人至少分到一個蘋果)
在第5行程式碼“children = int(input("請輸入來了幾個小朋友:"))”的下方新增一個if 語句,實現當蘋果的數量小於小朋友的數量時,應用raise 語句丟擲一個ValueError 異常,接下來再在最後一行語句的下方新增except 語句處理ValueError 異常。
def division(): '''功能:分蘋果''' print("\n=====================分蘋果======================\n") apple = int(input("請輸入蘋果的個數:"))# 輸入蘋果的數量 children = int(input("請輸入來了幾個小朋友:")) if apple < children: raise ValueError("蘋果太少了,不夠分...") result = apple//children# 計算每人分幾個蘋果 remain = apple - result*children# 計算餘下幾個蘋果 if remain>0: print(apple,"個蘋果,平均分給",children,"個小朋友,每人分",result,"個,剩下",remain,"個。") else: print(apple,"個蘋果,平均分給",children,"個小朋友,每人分",result,"個。") if __name__ == '__main__': try:# 捕獲異常 division()# 呼叫分蘋果的函式 except ZeroDivisionError:# 處理ZeroDivisionError異常 print("\n出錯了 ~_~ ——蘋果不能被0個小朋友分!") except ValueError as e:# ValueError print("\n出錯了~_~ ——",e)
執行程式:
說明:在應用raise 丟擲異常時,要儘量選擇合理的異常物件,而不應該丟擲一個與實際內容不相關的異常。流入,在例項03中,想要處理的是一個和值有關的異常,這時就不應該丟擲一個IndentationError異常。