python3基礎篇(十)——異常處理
前言:
閱讀這篇文章我能學到什麼?
這篇文章將爲你介紹python3中的異常捕獲和處理,如果你看過《代碼大全2》會明白爲程序設計上異常的處理是多麼重要的一件事。如果你希望對它有一些基礎的瞭解,那麼請讀這篇文章。
——如果你覺得這是一篇不錯的博文,希望你能給一個小小的贊,感謝您的支持。
目錄
1 程序異常處理
程序異常就是程序的運行結果超出了設計者的預料,程序的運行是“非正常”的執行流程。程序的異常處理其實應該分兩個階段,第一個階段是異常的檢測(識別出異常狀態,並區分出是何種異常),第二個階段是針對特定異常情況應該做何種處理(處理可以是忽略、修正、甚至重啓)。變成語言支持異常處理已經不是什麼“新鮮”的事了,但還是要提一下早期程序處理異常是用 error code 的方式,即函數或代碼段返回故障碼,通過故障碼來區分異常種類和決定如何處理。這種方式已經日漸淘汰,現在很多編程語言已經對異常處理有了較好的支持,形式通常是 try-catch ,在python3中是 try-except形式。斷言是一種常用的異常處理,它一般用於調試階段(發行版一般將其關閉)。
1.1 assert(斷言)
也其他語言的斷言處理一樣,當斷言的條件爲False
時,觸發異常進行斷言處理。斷言用於在程序檢測到異常時立即終止程序的運行並拋出此時的異常,不必等到後續運行到程序崩潰,拋出一大堆非根源錯誤信息。代碼大全的防禦式編程思想是建議在開發調試階段使問題儘可能“擴大化”,讓小問題也無法被忽視(小問題也導致程序運行終止,這樣有利於我們寫出健康強壯的代碼)。
我們嘗試實現一個功能函數,爲它加上斷言來捕獲一些異常。
代碼示例:
def Myabs(Num): #求數的絕對值
if Num >= 0:
RetValue = Num
else:
RetValue = -Num
return RetValue
print(Myabs(1)) #整數
print(Myabs(0))
print(Myabs(-1))
print(Myabs(-1.0)) #浮點數
運行結果:
1
0
1
1.0
這個函數用於求一個數的絕對值,看上去似乎沒有問題,因爲我們進行了一些簡單測試後發現結果符合我們的預期。但是如果這個代碼交到客戶手中將會出現嚴重的bug,因爲你無法現象客戶可能給這個函數傳遞什麼奇葩的內容進去。
進行如下測試:
def Myabs(Num): #求數的絕對值
if Num >= 0:
RetValue = Num
else:
RetValue = -Num
return RetValue
#預料之外的輸入
#print(Myabs("1")) #輸入字符串
#print(Myabs(1 + 2j)) #複數
#print(Myabs([1])) #列表
這幾種非法輸入全部都將報錯,因爲這個功能函數異常很可能導致最後整個程序運行異常。我們需要對輸入進行一些限制,現在在講斷言,我們就嘗試用斷言進行這些異常處理(假設我希望非法輸入時程序立即停止運行並告訴我發生了什麼異常,不要在非法輸出的情況下進行後續處理)。
assert的用法非常簡單:
語法結構:
assert expression
當expression
結果爲False
時觸發斷言,它將立即終止程序運行並拋出異常信息。
代碼示例:
def Myabs(Num): #求數的絕對值
#斷言處理,只有Num爲int或float時纔可以往下運行
assert isinstance(Num, int) or isinstance(Num, float)
if Num >= 0:
RetValue = Num
else:
RetValue = -Num
return RetValue
print(Myabs(1)) #整數
print(Myabs(0))
print(Myabs(-1))
print(Myabs(-1.0)) #浮點數
#預料之外的輸入
print(Myabs(1 + 2j)) #複數,運行到這裏觸發斷言程序就運行結束了
print(Myabs("1")) #輸入字符串
print(Myabs([1])) #列表
運行結果:
1
0
1
1.0
Traceback (most recent call last):
File "C:/Users/think/Desktop/Python_Test/Test.py", line 17, in <module>
print(Myabs(1 + 2j)) #複數,運行到這裏觸發斷言程序就運行結束了
File "C:/Users/think/Desktop/Python_Test/Test.py", line 3, in Myabs
assert isinstance(Num, int) or isinstance(Num, float)
AssertionError
可以看到編譯器其實也檢測到類型問題拋出錯誤信息了,這種錯誤不是語法錯誤,編譯器不能在編譯的時候檢查出錯誤,只能在運行的時候。也不能指望測試的時候能100%覆蓋到所有輸入可能(當然我這裏舉的例子很簡單,稍微有點經驗的程序員都能想到非法輸入的異常),加入異常就是爲了處理髮生預料之外的情況。斷言在異常時停止程序運行,使得在小的“問題”也無法被忽略,提醒程序設計者“這裏”有“超出預料”發生。可以看到異常信息assert isinstance(Num, int) or isinstance(Num, float)
,提示我們是程序什麼地方在拋出這個異常。
1.2 try異常捕獲和處理
python3提供了較爲良好的異常處理機制。下面具體介紹每種語法結構的用法。
1.2.1 try-except結構
語法結構:
try:
<CodeBlock>
except:
<CodeBlock>
try
代碼塊運行時會監視是否有異常事件拋出(可以是python3自動拋出的異常,也可以由設計者主動拋出異常)。如果檢測到異常拋出會執行except
後面的代碼塊,沒有異常則忽略這部分代碼。
還需要注意的是當try
中運行到拋出異常位置時,將不會繼續執行後續的代碼。except
後可以接具體的異常類型,一個 try-except結構可以有多個except
,一個except
後可以寫多個異常(要以元組形式寫出)。當except
後不寫異常類型時則表示針對所有異常類型。
代碼示例:
def Function(Num1, Num2):
try: #捕捉try代碼塊的異常
RetValue = Num1 / Num2
print("Here") #上面除式發生異常時不會運行到這裏
except: #發生異常後怎麼處理,沒有指定異常類型則針對所有類異常
print(f"error: {Num1}, {Num2}")
RetValue = Num1
return RetValue
print(Function(1, 2))
print("-------------------------------")
print(Function(1, 0))
運行結果:
Here
0.5
-------------------------------
error: 1, 0
1
Num2爲0時python3會自動拋出異常,這個異常被捕獲並執行except
後(沒有指明針對何種異常類型,是針對所有異常類型)的代碼快處理異常。
Num2爲0進行除法的異常是python3自己拋出的,我們嘗試主動拋出異常。拋出異常需要使用關鍵字raise
,後面接拋出的異常類型。
代碼示例:
def Function(Num1, Num2):
try: #捕捉try代碼塊的異常
if Num1 > 100 or Num2 > 100:
raise ValueError #對輸入值進行檢查,主動拋出異常
print("Here") #拋出異常後後面的代碼不會繼續執行
RetValue = Num1 / Num2
except ZeroDivisionError: #除數爲0異常,爲python自動拋出
print("ZeroDivisionError")
RetValue = Num1
except ValueError: #值異常,設計者主動拋出
print("ValueError")
RetValue = 0
except: #針對所有類型異常
print("All error")
RetValue = 0
return RetValue
print(Function(1, 0))
print("-------------------------------")
print(Function(1, 101))
print("-------------------------------")
print(Function(1, "2")) #異常輸入
運行結果:
ZeroDivisionError
1
-------------------------------
ValueError
0
-------------------------------
All error
0
當try中拋出異常時停止try
代碼塊的執行,開始在except
語句中查找能匹配拋出異常的分支(就像if-elif語句那樣從上往下找到滿足條件的分支執行裏面的代碼塊),找到後就執行相應except
後面的代碼塊。沒有寫明異常類型的except
分支是針對所有異常類型。raise
後接需要拋出的異常類型,raise
後面的代碼不會被執行,因爲異常已經拋出了。
給一個except
分支指定多個異常類型。
代碼示例:
def Function(Choose):
try:
if Choose == 1:
raise ValueError
elif Choose == 2:
raise ZeroDivisionError
except (ValueError, ZeroDivisionError): #一個分支處理多種異常,元組形式給出異常類型
print("error")
Function(1)
Function(2)
運行結果:
error
error
ValueError
和ZeroDivisionError
都是python3的內置異常類型,也可以自己定義異常類型。關於異常類型放在這篇文章的後面講。
1.2.2 try-except-else結構
try-except 結構基礎上可以繼續添加else
分支,當try中沒有拋出異常時將會執行else
後的代碼塊,若有任何異常拋出則不會執行。注意,except
分支和else
分支並不是一定“互斥”執行的,若try中拋出異常但是except
找不到該類異常匹配的分支,則既不會執行except
也不會執行else
後的代碼塊。
語法結構:
try:
<CodeBlock>
except Error:
<CodeBlock>
else:
<CodeBlock>
代碼示例:
def Function(Choose):
try:
if Choose == 1:
raise ValueError
elif Choose == 2:
raise ZeroDivisionError
else:
pass #不拋出異常
except ValueError: #一個分支處理多種異常,元組形式給出異常類型
print("error")
else:
print("No error")
#Function(1) #try拋出異常並且except具有該類異常的匹配分支,不會執行else
#Function(2) #try拋出異常並且except沒有該類異常的匹配分支,不會執行else
Function(3)
運行結果:
No error
else
分支就是無異常時要做什麼處理。
1.2.3 try-except-finally結構
finally
分支不論是否有異常拋出都會被執行。 try-except-finally 結構也可以結合上else
分支變成 try-except-else-finally 結構。
語法結構:
try:
<CodeBlock>
except Error:
<CodeBlock>
finally:
<CodeBlock>
或者
try:
<CodeBlock>
except Error:
<CodeBlock>
else:
<CodeBlock>
finally:
<CodeBlock>
代碼示例:
def Function(Choose):
try:
if Choose == 1:
raise ValueError
else:
pass #不拋出異常
except ValueError: #一個分支處理多種異常,元組形式給出異常類型
print("error")
else:
print("No error")
finally:
print("Always Run")
Function(1)
print("-------------------")
Function(2)
運行結果:
error
Always Run
-------------------
No error
Always Run
當有異常時如果except
分支裏具有該類異常的匹配項,則執行該分支,沒有則不執行except
分支,但是一定會執行finally
分支。當沒有異常時,出了執行else
分支還要執行finally
分支。所以含有finally
的異常結構可能有兩個分支都被執行。
2 異常結構的嵌套
與 if-elif 結構類似的,異常結構也能進行嵌套。嵌套時外層異常結構可以將內層異常結構看做代碼塊,當外層try代碼塊內的內層異常結構,有異常拋出時(指內層處理不了把異常往外層拋出),會被外層的try捕獲,然後在except
中尋找匹配的分支執行。當內層能處理自己的異常時,異常不被拋到外層,對外層try
來說就是沒有異常發生。異常嵌套的執行規則和非嵌套結構一樣,把內層異常結構看做代碼塊就可以了。
代碼示例:
def Function(Choose):
try:
try:
if Choose == 1:
raise ValueError
elif Choose ==2:
raise ZeroDivisionError #拋出內層不能處理的異常,則後續代碼運行將異常拋給外層
except ValueError:
print("Deal ValueError")
print("Here. In try") #內層try有拋出未處理的異常時,會被外層繼續捕獲到這個異常,內層代碼會停止執行
except ZeroDivisionError:
print("Deal ZeroDivisionError")
print("Here. Out try")
Function(1)
print("------------------------")
Function(2) #內層的print函數不會執行
運行結果:
Deal ValueError
Here. In try
Here. Out try
------------------------
Deal ZeroDivisionError
Here. Out try
在嵌套異常結構類,內層循環不能處理(except
沒有匹配的分支)的異常會繼續拋出到外層,拋出異常後內層循環的代碼會停止往後繼續執行。總之就是考慮外層時,把內層看作代碼塊去理解。
內層也可以主動將異常拋出給外層處理。
代碼示例:
def Function(Choose):
try:
try:
if Choose == 1:
raise ValueError
except ValueError:
print("raise ValueError")
raise #將此異常繼續往上層拋出
except: #處理所有內層異常
print("Deal All error")
print("Here. In try") #內層try有拋出未處理的異常時,會被外層繼續捕獲到這個異常,內層代碼會停止執行
except ValueError:
print("Deal ValueError")
print("Here. Out try")
Function(1)
運行結果:
raise ValueError
Deal ValueError
Here. Out try
3 異常類型
上面涉及到了兩種(ValueError
和ZeroDivisionError
)python3內置的異常類型,python3提供了很多內置的異常類型,比如:
異常類型 | 含義 |
---|---|
SyntaxError | 語法錯誤 |
TypeError | 對類型無效的操作 |
ValueError | 傳入無效的參數 |
OverflowError | 數值運算超出最大限制 |
AssertionError | 斷言語句失敗 |
AttributeError | 對象沒有這個屬性 |
等等,太多了不一一列舉。這些異常種類是python3爲我們定義好的,設計者可以自己定義屬於自己的異常類。定義異常類需要繼承Exception
異常基類,自定義的異常類可以繼續被繼承,它們依然是異常類。
打碼示例:
import sys
class MyException(Exception):
def __init__(self, Msg): #重載構造函數
self.Msg = Msg #故障信息
class MyInputException(MyException):
def __init__(self, Msg, File, FunName, Line): # 重載構造函數
super().__init__(f"MyInputException --File: {File}, --FunName: {FunName} --Line: {Line}, --Msg: {Msg}")
class MyOutputException(MyException):
def __init__(self, Msg, File, FunName, Line): # 重載構造函數
super().__init__(f"MyOutputException --File: {File}, --FunName: {FunName} --Line: {Line}, --Msg: {Msg}")
def Function(Choose):
try:
if Choose == 1:
raise MyInputException("Error Test", sys._getframe().f_code.co_filename, #當前文件名
sys._getframe().f_code.co_name, #當前函數名
sys._getframe().f_lineno) #當前行號
elif Choose == 2:
raise MyOutputException("Error Test", sys._getframe().f_code.co_filename,
sys._getframe().f_code.co_name,
sys._getframe().f_lineno)
except MyException as E:
print(E.Msg)
Function(1)
Function(2)
運行結果:
MyInputException --File: C:/Users/think/Desktop/Python_Test/Test.py, --FunName: Function --Line: 20, --Msg: Error Test
MyOutputException --File: C:/Users/think/Desktop/Python_Test/Test.py, --FunName: Function --Line: 24, --Msg: Error Test
MyInputException
和MyOutputException
自定義異常類繼承於自定義異常類MyException
,總之自定義異常類必須直接或間接繼承於Exception
類,這是python3內置的異常基類。as
關鍵字給異常類型取了別名,方便通過別名(對象)訪問自定義類的成員變量或方法(比如E.Msg
)。