前言:之前作業時用python完成一個利用串口發SCPI與單片機互動通信的命令列視窗,在實作功能的時候發現用python對資料結果無論是最終正確值的回傳還是錯誤值的回傳都可以直接return給主界面,顯然直接return不同含義的資料是不行的,所以采用例外機制來處理錯誤值的資料,因為之前對例外這方面了解的比較少,在此查了點資料并整理個小筆記,
文章目錄
- 一、對例外的理解
- 1、什么是例外
- 2、錯誤和例外的區別
- 3、常見python例外種類
- 二、python五大例外處理機制
- 1、默認例外處理機制
- 2、try....except....處理機制
- 3、try...except...finally.....處理機制
- 4、assert斷言處理機制
- 5、with...as處理機制
- 三、python例外自定義
- 1、例外自定義
- 2、例外拋出raise
- 3、例外捕獲
- 四、例外使用注意事項
- 1、不要太依賴例外機制
- 2、不要在 try 塊中引入太多的代碼
- 3、不要忽略捕獲到的例外
- 總結
一、對例外的理解
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 | 數值運算超出最大限制 |
| ZeroDivisionError | 除(或取模)零 (所有資料型別) |
| 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! #例外沒被捕獲,也執行了finally
Traceback (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! #例外沒被捕獲,也執行了finally
Traceback (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) # 拋出例外物件,傳入錯誤碼1
def demo_main():
try:
fun()
except MyException as ex: # 這里要使用MyException進行捕獲,物件為ex
print(ex) # 輸出的是__str__部分回傳的內容,即“接收錯誤”
print(ex.error_num) # 輸出的是__init__中定義的error_num,即1
demo_main() #此處開始運行
??代碼從demo_main函式開始執行,進入try陳述句塊,陳述句塊中的fun()函式模擬代碼運行失敗時raise 自定義的例外,except 正常接收后通過as 關鍵字得到例外物件,訪問該例外物件,便可正常輸出自定義的例外資訊和自定義的錯誤碼,
四、例外使用注意事項
此注意事項參考博文:例外機制使用細則.
1、不要太依賴例外機制
??python 的例外機制非常方便,對于資訊的傳遞中十分好用(這里資訊的傳遞主要有三種,引數傳遞,全域變數傳遞,以及例外機制傳遞),但濫用例外機制也會帶來一些負面影響,過度使用例外主要表現在兩個方面:①把例外和普通錯誤混淆在一起,不再撰寫任何錯誤處理代碼,而是以簡單地引發例外來代苦所有的錯誤處理,②使用例外處理來代替流程控制,例子如下:
buf = "hello"
#例1:使用例外處理來遍歷arr陣列的每個元素
try:
i = 0
while True:
print (buf [i])
i += 1
except:
pass
#例2:使用流程控制避免下標訪問例外
i = 0
while 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例外使用時的注意事項,
Tips:本人能力有限,如有錯誤之處麻煩指出,放棄不難,但堅持一定很酷!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/267385.html
標籤:python
