python3基礎篇(十)——異常處理
前言:
閱讀這篇文章我能學到什麼?
這篇文章将為你介紹python3中的異常捕獲和處理,如果你看過《代碼大全2》會明白為程式設計上異常的處理是多麼重要的一件事。如果你希望對它有一些基礎的了解,那麼請讀這篇文章。
——如果你覺得這是一篇不錯的博文,希望你能給一個小小的贊,感謝您的支援。
目錄
- python3基礎篇(十)——異常處理
-
- 1 程式異常處理
-
- 1.1 assert(斷言)
- 1.2 try異常捕獲和處理
-
- 1.2.1 try-except結構
- 1.2.2 try-except-else結構
- 1.2.3 try-except-finally結構
- 2 異常結構的嵌套
- 3 異常類型
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
)。