前言:之前工作時用python完成一個利用串列埠發SCPI與微控制器互動通訊的命令列視窗,在實現功能的時候發現用python對資料結果無論是最終正確值的返回還是錯誤值的返回都可以直接return給主介面。顯然直接return不同含義的資料是不行的,所以採用異常機制來處理錯誤值的資料。因為之前對異常這方面瞭解的比較少,在此查了點資料並整理個小筆記。
文章目錄
總結
(免費學習推薦:)
一、對異常的理解
1、什麼是異常
異常即「與正常情況不同」,何為正常?正常便是直譯器在解釋程式碼時,我們所編寫的程式碼符合直譯器定義的規則,即為正常,當直譯器發現某段程式碼符合語法但有可能出現不正常的情況時,直譯器便會發出一個事件,中斷程式的正常執行。這個中斷的訊號便是一個異常訊號。所以,總體解釋就是,在直譯器發現到程式出現錯誤的時候,則會產生一個異常,若程式沒有處理,則會將該異常丟擲,程式的執行也隨之終止。我們可以在一個空白的.py檔案中寫一句int(「m」),執行後結果如下。
這一串字型為直譯器丟擲的一系列錯誤資訊,因為int()傳入的引數只支援數位字串和數位,顯然‘m’不屬於數位字串傳入引數錯誤所以直譯器報「valueError」的錯誤。
2、錯誤和異常的區別
對於python錯誤的概述:它指的是程式碼執行前的語法或邏輯錯誤。拿常規語法錯誤來說,當我們編寫的程式碼過不了語法檢測時,則會直接出現語法錯誤,必須在程式執行前就改正,不然寫的程式碼將毫無意義,程式碼是不執行的,也無法捕獲得到。舉個例子,在.py檔案輸入if a = 1 print(「hello」),輸出結果如下:
Traceback (most recent call last): File "E:/Test_code/test.py",line 1 if a = 1 print("hello") ^SyntaxError: invalid syntax
函數 print() 被檢查到有錯誤,是它前面缺少了一個冒號 : ,所以解析器會復現句法錯誤的那行程式碼,並用一個小「箭頭」指向行裡檢測到的第一個錯誤,所以我們可以直接找到對應的位置修改其語法。當然除了語法錯誤,還有很多程式奔潰的錯誤,如記憶體溢位等,這類錯誤往往比較隱蔽。
相比於錯誤,python異常主要在程式執行過程中,程式遇見邏輯或演演算法問題,這時直譯器如果可以處理,則沒問題,如果處理不了,便直接終止程式,便將異常丟擲,如第1小點的int(‘m’)例子,因為引數傳入錯誤導致程式出錯。這種因為邏輯產生的異常五花八門,還好我們的直譯器都內建好了各種異常的種類,讓我們知道是什麼樣的異常出現,好讓我們「對症下藥」。
這裡注意一點,上述語法錯誤是可識別的錯誤,所以直譯器也會預設丟擲一個SyntaxError異常資訊反饋給程式設計師。所以本質上大部分錯誤都是可被輸出列印的,只是因為錯誤程式碼不執行,也就沒法處理,所以捕獲錯誤的異常資訊就變得沒意義。
3、常見python異常種類
這裡貼上我們在寫程式碼時最常見的異常型別,如果遇到其他種類的異常,當然是選擇白度啦~
異常名稱 | 名稱解析 |
---|---|
BaseException | 所有異常的基礎類別 |
SystemExit | 直譯器請求退出 |
KeyboardInterrupt | 使用者中斷執行(通常是輸入^C) |
Exception | 常規錯誤的基礎類別 |
StopIteration | 迭代器沒有更多的值 |
GeneratorExit | 生成器(generator)發生異常來通知退出 |
StandardError | 所有的內建標準異常的基礎類別 |
ArithmeticError | 所有數值計算錯誤的基礎類別 |
FloatingPointError | 浮點計算錯誤 |
OverflowError | 數值運算超出最大限制 |
ZeropisionError | 除(或取模)零 (所有資料型別) |
AssertionError | 斷言語句失敗 |
AttributeError | 物件沒有這個屬性 |
EOFError | 沒有內建輸入,到達EOF 標記 |
EnvironmentError | 作業系統錯誤的基礎類別 |
IOError | 輸入/輸出操作失敗 |
OSError | 作業系統錯誤 |
WindowsError | 系統呼叫失敗 |
ImportError | 匯入模組/物件失敗 |
LookupError | 無效資料查詢的基礎類別 |
IndexError | 序列中沒有此索引(index) |
KeyError | 對映中沒有這個鍵 |
MemoryError | 記憶體溢位錯誤(對於Python 直譯器不是致命的) |
NameError | 未宣告/初始化物件 (沒有屬性) |
UnboundLocalError | 存取未初始化的本地變數 |
ReferenceError | 弱參照(Weak reference)試圖存取已經垃圾回收了的物件 |
RuntimeError | 一般的執行時錯誤 |
NotImplementedError | 尚未實現的方法 |
SyntaxError Python | 語法錯誤 |
IndentationError | 縮排錯誤 |
TabError Tab | 和空格混用 |
SystemError | 一般的直譯器系統錯誤 |
TypeError | 對型別無效的操作 |
ValueError | 傳入無效的引數 |
UnicodeError Unicode | 相關的錯誤 |
UnicodeDecodeError Unicode | 解碼時的錯誤 |
UnicodeEncodeError Unicode | 編碼時錯誤 |
UnicodeTranslateError Unicode | 轉換時錯誤 |
Warning | 警告的基礎類別 |
DeprecationWarning | 關於被棄用的特徵的警告 |
FutureWarning | 關於構造將來語意會有改變的警告 |
OverflowWarning | 舊的關於自動提升為長整型(long)的警告 |
PendingDeprecationWarning | 關於特性將會被廢棄的警告 |
RuntimeWarning | 可疑的執行時行為(runtime behavior)的警告 |
SyntaxWarning | 可疑的語法的警告 |
UserWarning | 使用者程式碼生成的警告 |
二、python五大例外處理機制
我們明白了什麼是異常後,那麼發現異常後怎麼處理,便是我們接下來要解決的問題。這裡將處理異常的方式總結為五種。
1、預設例外處理機制
「預設」則說明是直譯器預設做出的行為,如果直譯器發現異常,並且我們沒有對異常進行任何預防,那麼程式在執行過程中就會中斷程式,呼叫python預設的例外處理器,並在終端輸出異常資訊。剛才舉過的例子:int(「m」),便是直譯器因為發現引數傳入異常,這種異常直譯器「無能為力」,所以它最後中斷了程式,並將錯誤資訊列印輸出,告訴碼農朋友們:你的程式有bug!!!
2、try…except…處理機制
我們把可能發生錯誤的語句放在try語句裡,用except來處理異常。每一個try,都必須至少有一個或者多個except。舉一個最簡單的例子如下,在try存取number的第500個元素,很明顯陣列越界存取不了,這時候直譯器會發出異常訊號:IndexError,接著尋找後面是否有對應的異常捕獲語句except ,如果有則執行對應的except語句,待except語句執行完畢後,程式將繼續往下執行。如果沒有對應的except語句,即使用者沒有處理對應的異常,這時直譯器會直接中斷程式並將錯誤資訊列印輸出。
number = 'hello'try: print(number[500]) #陣列越界存取except IndexError: print("下標越界啦!")except NameError: print("未宣告物件!")print("繼續執行...")
輸出結果如下,因為直譯器發出異常訊號是IndexError,所以執行下標越界語句。
下標越界啦! 繼續執行...
為了解鎖更多用法,我們再將例子改一下,我們依然在try存取number的第500個元素,造成存取越界錯誤,這裡的except用了as關鍵字可以獲得異常物件,這樣子便可獲得錯誤的屬性值來輸出資訊。
number = 'hello'try: print(number[500]) #陣列越界存取except IndexError as e: print(e)except Exception as e: #萬能異常 print(e)except: #預設處理所有異常 print("所有異常都可處理")print("繼續執行...")
輸出結果如下所示,會輸出系統自帶的提示錯誤:string index out of range,相對於直譯器因為異常自己丟擲來的一堆紅色刺眼的字型,這種看起來舒服多了(能夠「運籌帷幄」的異常才是好異常嘛哈哈哈)。另外這裡用到「萬能異常」Exception,基本所有沒處理的異常都可以在此執行。最後一個except表示,如果沒有指定異常,則預設處理所有的異常。
string index out of range繼續執行...
3、try…except…finally…處理機制
finally語句塊表示,無論異常發生與否,finally中的語句都要執行完畢。也就是可以很霸氣的說,無論產生的異常是被except捕獲到處理了,還是沒被捕獲到直譯器將錯誤輸出來了,都統統要執行這個finally。還是原來簡單的例子加上finally語句塊如下,程式碼如下:
number = 'hello'try: print(number[500]) #陣列越界存取,丟擲IndexError異常except IndexError: print("下標越界啦!")finally: print("finally!")print("繼續執行...") #執行
結果如下,資料越界存取異常被捕獲到後,先執行except 語句塊,完畢後接著執行了finally語句塊。因為異常被執行,所以後面程式碼繼續執行。
下標越界啦!finally! 繼續執行...
對try語句塊進行修改,列印abc變數值,因為abc變數沒定義,所以會出現不會被捕獲的NameError異常訊號,程式碼如下所示:
number = 'hello'try: print(abc) #變數未被定義,丟擲NameError異常except IndexError: print("下標越界啦!")finally: print("finally!")print("繼續執行...") #不執行
結果如下,因為NameError異常訊號沒法被處理,所以直譯器將程式中斷,並將錯誤資訊輸出,但這過程中依然會執行finally語句塊的內容。因為程式被迫中斷了,所以後面程式碼不執行。
finally! #異常沒被捕獲,也執行了finallyTraceback (most recent call last): File "E:/Test_code/test.py",line 3,in <module> print("abc")NameError: name 'abc' is not defined
理解到這裡,相信:try…finally…這種機制應該也不難理解了,因為省略了except 捕獲異常機制,所以異常不可能被處理,直譯器會將程式中斷,並將錯誤資訊輸出,但finally語句塊的內容依然會被執行。例子程式碼如下:
number = 'hello'try: print(abc) #變數未被定義,丟擲NameError異常finally: print("finally!")print("繼續執行...")
執行結果:
finally! #異常沒被捕獲,也執行了finallyTraceback (most recent call last): File "E:/Test_code/test.py",line 3,in <module> print("abc")NameError: name 'abc' is not defined
4、assert斷言處理機制
assert語句先判斷assert後面緊跟的語句是True還是False,如果是True則繼續往下執行語句,如果是False則中斷程式,將錯誤資訊輸出。
assert 1 == 1 #為True正常執行assert 1 == 2 #為False,終止程式,錯誤資訊輸出
5、with…as處理機制
with…as一般常用在檔案處理上,我們平時在使用類似檔案的流物件時,使用完畢後要呼叫close方法關閉,很麻煩,這裡with…as語句提供了一個非常方便且人性的替代方法,即使突發情況也能正常關閉檔案。舉個例子程式碼如下,open開啟檔案後將返回的檔案流物件賦值給fd,然後在with語句塊中使用。
with open('e:/test.txt','r') as fd: fd.read() print(abc) #變數未被定義,程式終止,錯誤資訊輸出print("繼續執行...")
正常情況下,這裡的with語句塊完畢之後,會自動關閉檔案。但如果with語句執行中發生異常,如程式碼中的變數未定義異常,則會採用預設例外處理機制,程式終止,錯誤資訊輸出,後面程式碼不被執行,檔案也會正常關閉。
三、python異常自定義
說了這麼多異常的使用,終於可以回到我前言所說的在實際專案中存在的問題,即錯誤碼的返回和數值的返回是衝突的(因為錯誤碼也是數值),這時候便可以用異常的丟擲和捕獲來完成錯誤碼的傳遞,即try和except 。但系統發生異常時丟擲的是系統本身定義好的異常型別,跟自己的錯誤碼又有何關係?這就是我接下來要說的內容:如何定義自己的異常並且能夠被except 所捕獲。
1、異常自定義
實際開發中,有時候系統提供的異常型別往往都不能滿足開發的需求。這時候就要使用到異常的自定義啦,你可以通過建立一個新的異常類來擁有自己的異常。自己定義的異常類繼承自 Exception 類,可以直接繼承,或者間接繼承。栗子舉起來:
class MyException(Exception): '''自定義的異常類''' def __init__(self, error_num): #異常類物件的初始化屬性 self.error_num = error_num def __str__(self): #返回異常類物件說明資訊 err_info = ['超時錯誤','接收錯誤'] return err_info[self.error_num]
該類繼承自Exception 類,並且新類的名字為MyException,這跟前面我們一直在用的IndexError這個異常類一樣,都是繼承自Exception 類。__init__
為建構函式,當我們建立物件時便會自動呼叫,__str__
為物件說明資訊函數,當使用print輸出物件的時候,只要自己定義了__str__方法,那麼就會列印從在這個方法中return的資料。
即print(MyException(0))時,便可列印「超時錯誤」這個字串,print(MyException(1))時,便可列印「接收錯誤」這個字串,心細的你應該可以理解,MyException(x)為臨時物件(x是傳入錯誤碼引數,這裡只定義了0和1),與a = MyException(x),a為物件一個樣子 。 這裡有一個好玩的說法,在python中方法名如果是__xxxx__()的,那麼就有特殊的功能,因此叫做「魔法」方法。
2、異常丟擲raise
現在我們自己定義的錯誤定義好了(上面的MyException),怎麼能像IndexError一樣讓except捕獲到呢?於是乎raise關鍵字派上用場。我們在異常機制中用try…except時,一般都是將可能產生的錯誤程式碼放到try語句塊中,這時出現異常則系統便會自動將其丟擲,比如IndexError,這樣except就能捕獲到,所以我們只要將自定義的異常在需要的時候將其丟擲即可。
raise 唯一的一個引數指定了要被丟擲的異常。它必須是一個異常的範例或者是異常的類(也就是 Exception 的子類),那麼我們剛剛定義的異常類就可以用啦,舉個簡單例子:
try: raise MyException(0) # 自己定義的錯誤類,將錯誤碼為0的錯誤丟擲except MyException as e: print(e) # 輸出的是__str__返回的內容,即「超時錯誤」
這裡我直接將自己定義的錯誤丟擲,…as e就是把得到的錯誤當成物件e,這樣才可以存取其屬性和方法。因為自己定義的錯誤中可以支援多個錯誤碼(本質還是MyException這個錯誤),所以便可實現傳入不同錯誤碼就可列印不同錯誤資訊。
3、異常捕獲
只要我們在try中將錯誤raise出來,except就可以捕獲到(當然,異常必須是Exception 子類才能被捕獲),將前面兩個例子整合起來,程式碼如下:
'''錯誤碼:0代表超時錯誤,1代表接收錯誤'''class MyException(Exception): '''自定義的異常類''' def __init__(self, error_num): # 異常類物件的初始化屬性 self.error_num= error_num def __str__(self): # 返回異常類物件指定錯誤碼的資訊 err_info = ['超時錯誤','接收錯誤'] return err_info[self.error_num]def fun() raise MyException(1) # 丟擲異常物件,傳入錯誤碼1def demo_main(): try: fun() except MyException as ex: # 這裡要使用MyException進行捕獲,物件為ex print(ex) # 輸出的是__str__部分返回的內容,即「接收錯誤」 print(ex.error_num) # 輸出的是__init__中定義的error_num,即1demo_main() #此處開始執行
程式碼從demo_main函數開始執行,進入try語句塊,語句塊中的fun()函數模擬程式碼執行失敗時raise 自定義的異常,except 正常接收後通過as 關鍵字得到異常物件,存取該異常物件,便可正常輸出自定義的異常資訊和自定義的錯誤碼。
四、異常使用注意事項
此注意事項參考博文:異常機制使用細則.
1、不要太依賴異常機制
python 的異常機制非常方便,對於資訊的傳遞中十分好用(這裡資訊的傳遞主要有三種,引數傳遞,全域性變數傳遞,以及異常機制傳遞),但濫用異常機制也會帶來一些負面影響。過度使用異常主要表現在兩個方面:①把異常和普通錯誤混淆在一起,不再編寫任何錯誤處理程式碼,而是以簡單地引發異常來代苦所有的錯誤處理。②使用例外處理來代替流程控制。例子如下:
buf = "hello"#例1:使用例外處理來遍歷arr陣列的每個元素try: i = 0 while True: print (buf [i]) i += 1except: pass#例2:使用流程控制避免下標存取異常i = 0while i < len(buf ): print(buf [i]) i += 1
例1中假如迴圈過度便會下標存取異常,這時候把錯誤丟擲,再進行一系列處理,顯然是不可取的,因為異常機制的效率比正常的流程控制效率差,顯然例2中簡單的業務流程就可以避開這種錯誤。所以不要熟悉了異常的使用方法後,遇到這種簡單邏輯,便不管三七二十一引發異常後再進行解決。對於完全己知的錯誤和普通的錯誤,應該編寫處理這種錯誤的程式碼,增加程式的健壯性。只有對於外部的、不能確定和預知的執行時錯誤才使用異常。
2、不要在 try 塊中引入太多的程式碼
在 try 塊裡放置大量的程式碼,這看上去很「簡單」,程式碼框架很容易理解,但因為 try 塊裡的程式碼過於龐大,業務過於複雜,就會造成 try 塊中出現異常的可能性大大增加,從而導致分析異常原因的難度也大大增加。
而且當塊過於龐大時,就難免在 try 塊後緊跟大量的 except 塊才可以針對不同的異常提供不同的處理邏輯。在同一個 try 塊後緊跟大量的 except 塊則需要分析它們之間的邏輯關係,反而增加了程式設計複雜度。所以,可以把大塊的 try 塊分割成多個小塊,然後分別捕獲並處理異常。
3、不要忽略捕獲到的異常
不要忽略異常!既然己捕獲到異常,那麼 except 塊理應做些有用的事情,及處理並修復異常。except 塊整個為空,或者僅僅列印簡單的異常資訊都是不妥的!具體的處理方式為:
①處理異常。對異常進行合適的修復,然後繞過異常發生的地方繼續執行;或者用別的資料進行計算,以代替期望的方法返回值;或者提示使用者重新操作,總之,程式應該儘量修復異常,使程式能恢復執行。
②重新引發新異常。把在當前執行環境下能做的事情儘量做完,然後進行異常轉譯,把異常包裝成當前層的異常,重新傳給上層呼叫者。
③在合適的層處理異常。如果當前層不清楚如何處理異常,就不要在當前層使用 except 語句來捕獲該異常,讓上層呼叫者來負責處理該異常。
總結
本文從系統預設的異常起手,說明了什麼是異常並總結了系統常見的異常類,接著寫了怎麼自定義異常,從異常的定義到丟擲再到獲取完成自定義異常的定義和使用,最後再總結了python異常使用時的注意事項。
相關免費學習推薦:(視訊)
以上就是讀懂Python的異常機制的詳細內容,更多請關注TW511.COM其它相關文章!