一、異常處理的意義
1. 異常機(jī)制已經(jīng)成為衡量一門編程語言是否成熟的標(biāo)準(zhǔn)之一,使用異常處理機(jī)制的Python程序會(huì)有更好的容錯(cuò)性。
2. 沒有人能夠保證自己寫的程序永遠(yuǎn)不會(huì)出錯(cuò),既是程序沒有錯(cuò)誤,也不能保證用戶按你的意圖來輸入,另外還有系統(tǒng)的穩(wěn)定性,計(jì)算硬件是否損壞,網(wǎng)絡(luò)掉線等諸多情況。
二、異常處理機(jī)制
1. 使用try...except捕獲異常
1)例如,通常在程序運(yùn)行時(shí),用戶可以隨意輸入,程序不會(huì)因?yàn)橛脩舻妮斎氩缓戏ǘ蝗婚g退出,而是向用戶提示輸入不合法,并請(qǐng)用戶再次輸入。
那么我們就希望有一種強(qiáng)大的if塊來解決這個(gè)非法輸入的問題:
if 用戶輸入不合法:
? ? alert 輸入不合法
? ? go retry
else:
????#業(yè)務(wù)實(shí)現(xiàn)代碼
2)但是“用戶輸入不合法”這個(gè)條件怎么定義呢?我們可以使用正則表達(dá)式與用戶的輸入進(jìn)行匹配。但現(xiàn)實(shí)中不合法的情況非常多,想讓程序一次處理所有的錯(cuò)誤,我們可以將上面的偽代碼修改為:
if 一切正常:
? ? #業(yè)務(wù)實(shí)現(xiàn)代碼
就是:
? ? alert 輸入不合法
? ? goto retry
3) 但“一切正?!币廊皇呛艹橄蟮?,無法轉(zhuǎn)化成代碼。在這種情形下Python提供了一種假設(shè):如果程序可以順利運(yùn)行,那么就是“一切正?!薄?b>由此得出Python異常處現(xiàn)機(jī)制的語法結(jié)構(gòu):
try:
? ? #業(yè)務(wù)實(shí)現(xiàn)代碼
except(Error1, Error2, ....) as e:
? ? alert 輸入不合法
? ? goto retry
4) 這樣如果執(zhí)行try塊里的業(yè)務(wù)實(shí)現(xiàn)代碼是出現(xiàn)異常,系統(tǒng)會(huì)自動(dòng)生成一個(gè)異常對(duì)象,該異常對(duì)象被提交給Python解釋器,這個(gè)過程被稱為引發(fā)異常。
5)當(dāng)Python解釋器收到異常對(duì)象時(shí),會(huì)尋找處理該異常對(duì)象的except塊,如果找到合適的except塊,則把該過程稱為捕捉異常。如果Python解釋器找到不捕獲異常的except塊,則運(yùn)行時(shí)環(huán)境終止,Python解釋器也將退出。
6)不管代碼塊是否處于try中,或except塊中,只要執(zhí)行該代碼時(shí)了現(xiàn)了異常,系統(tǒng)總會(huì)自動(dòng)生成一個(gè)Error對(duì)象。
7) try 可以有多個(gè)except塊,這是為了針對(duì)不同的異常類提供不同的異常處理方式。當(dāng)系統(tǒng)發(fā)生不同意外情況時(shí),系統(tǒng)會(huì)生成不同的異常對(duì)象。如果try被執(zhí)行一次,,則try后面只有一個(gè)except被執(zhí)行,除非放在循環(huán)中,使用continue開始下一次循環(huán)。
2. 異常類
1)Python的所有異常類的基類是BaseException, 但是如果用戶果自定義異常,則一概繼承Exception類。
2)BaseException的主要子類是Exception,所在不管是系統(tǒng)的異常類,還是用戶自定義異常類,都是從Exception派生。
3)異常捕獲實(shí)例:
>>> import sys
>>> try:
a = int(sys.argv[1]) # 代表當(dāng)前運(yùn)行的程序所提供的第一個(gè)參數(shù)
b = int(sys.argv[2])# 代表當(dāng)前運(yùn)行的程序所提供的第二個(gè)參數(shù)
c = a/b
print("您輸入的兩個(gè)數(shù)相除的結(jié)果是:",c)
except IndexError:
print("索引錯(cuò)誤:運(yùn)行程序時(shí)輸入的參數(shù)個(gè)數(shù)不夠")
except ValueError:
print("數(shù)值錯(cuò)誤:程序只能接受整數(shù)參數(shù)")
except ArithmeticError:
print("算術(shù)錯(cuò)誤")
except Exception:
print("未知異常")
3. 多異常捕獲:指一個(gè)Except塊可以捕獲多種類型的異常。例如:
>>> import sys
>>> try:
a = int(sys.argv[1])
b = int(sys.argv[2])
c = a/b
print("您輸入的兩個(gè)數(shù)相除的結(jié)果是:",c)
except (IndexError, ValueError, ArithmeticError):
print("程序發(fā)生了數(shù)組越界、格式錯(cuò)誤、算術(shù)異常之一")
except: # 此處的省略也是合法的,一般放在最后
print("未知異常")
程序發(fā)生了數(shù)組越界、格式錯(cuò)誤、算術(shù)異常之一
4. 訪問異常信息
1)如果程序需要在except塊中訪問異常對(duì)象的相關(guān)信息,則可以通過為異常對(duì)象聲明變量來實(shí)現(xiàn)。
2)所在的異常對(duì)象都包含如下幾個(gè)對(duì)象和方法:
args: 該屬性返回異常的錯(cuò)誤編號(hào)和描述字符串
errno:該屬性返回異常的錯(cuò)誤編號(hào)
strerror:該屬性返回異常的描述字符串
with_traceback():通過該方法可以處理異常的傳播軌跡
3)程序訪問異常信息實(shí)例:
>>> def foo():
try:
fis = open("a.txt")
except Exception as e:
print(e.args)
print(e.errno)
print(e.strerror)
>>> foo()
#運(yùn)行結(jié)果如下:
(2, 'No such file or directory')
2
No such file or directory
5. else塊
1) 在Python異常處理流程中還可添加一個(gè)else塊,當(dāng)try塊沒有出現(xiàn)異常時(shí),程序會(huì)執(zhí)行else塊。
例如:
>>> s = input("請(qǐng)輸入除數(shù):")
請(qǐng)輸入除數(shù):5
>>> try:
result = 20/int(s)
print('20除以%s的結(jié)果是:%g'%(s,result))
except ValueError:
print('值錯(cuò)誤,您必須輸入數(shù)值')
except ArithmeticError:
print('算術(shù)錯(cuò)誤,您不能輸入0')
else:
print('沒有出現(xiàn)異常')
#運(yùn)行結(jié)果如下:
20除以5的結(jié)果是:4
沒有出現(xiàn)異常
2)實(shí)際上大部分語言異常處理都沒有else塊,可以將else塊的內(nèi)容直接放到try后面。但Python異常處理使用else塊也不是多余的語法。因?yàn)樵趀lse塊中的異常不會(huì)被except捕獲,該異常會(huì)傳給Python解釋器導(dǎo)致程序中止。
3)如果希望某段代碼的異常,能被后面的except捕獲,那么就應(yīng)該放在try塊中;如果不希望被except捕獲,就應(yīng)該放在else塊中。
6. 使用finally回收資源
1)為了能夠保證回收try塊中打開的一些物理資源(如數(shù)據(jù)庫連接、網(wǎng)絡(luò)連接和磁盤文件),異常處理機(jī)制提供了finally塊。
2)Python 完整的異常處理語法結(jié)構(gòu)如下:
try:
? ? #業(yè)務(wù)實(shí)現(xiàn)代碼
except SubException1 as e:
? ? #異常處理塊1
except SubException2 as e:
? ? #異常處理塊2
else:
? ? #正常處理塊
finally:
? ? #資源回收塊
三、使用raise引發(fā)異常
1. 如果程序中數(shù)據(jù)或執(zhí)行與現(xiàn)實(shí)的需求不符,但是系統(tǒng)不會(huì)判斷這樣的異常,只能程序員來決定是否引發(fā)異常,此時(shí)可以使用raise語句來完成這種自行引發(fā)的異常。
2. raise語句三種常見的用法:
1) 單獨(dú)一個(gè)raise,該語句引發(fā)當(dāng)前上下文中捕獲的異常,或默認(rèn)引發(fā)RuntimeError異常。
2)raise后帶一個(gè)異常類,該語句引發(fā)指定的異常類。
3)raise后帶一個(gè)異常對(duì)象,該語句引發(fā)制定的異常對(duì)象。
以上三種最終都是引發(fā)一個(gè)異常實(shí)例,每次只能引發(fā)一個(gè)異常實(shí)例。
3. 用戶引發(fā)異常的兩種方式:raise和except,例如:
>>> def main():
try: # 使用try...except來捕獲異常,此時(shí)出現(xiàn)異常也不會(huì)傳給調(diào)用它的main()函數(shù)
mtd(3)
except Exception as e:
print('程序出現(xiàn)異常類是:',e)
mtd(3) #不使用try...except來捕獲異常,異常會(huì)傳播并導(dǎo)致程序中止
>>> def mtd(a):
if a>0:
raise ValueError("a的值大于0,不符合要求")
>>> main()
四、異常的傳播軌跡
1. 異常只要沒有被完全捕獲,異常就會(huì)從發(fā)生異常的函數(shù)或方法向外傳播,首先傳給該函數(shù)或方法的調(diào)用者,然后,,直到傳給Python解釋器,Python解釋器就會(huì)中止程序,并打印異常傳播的軌跡信息。所以通常我們看到大段的異常信息,并不一定發(fā)生很多嚴(yán)重的問題,可能只是一個(gè)異常引發(fā)的。
2. Python專門提供trackback模塊來處理異常傳播的軌跡,兩種常用的方法是:
1)trackback.print_exc(): 將異常傳播軌跡輸出到控制臺(tái)或文件中。
2) format_exc():將異常傳播軌跡轉(zhuǎn)換成字符串。
五、異常處理規(guī)則
1.? 不要過度使用異常處理,注意以下兩點(diǎn):
1) 需要編寫錯(cuò)誤處理代碼,而不是簡單的用異常來代替處理。
2)? 不能用異常來代替流程控制。
2.? 不要使用過度龐大的try塊:try塊越大,發(fā)生異常的可能性就越大。而且在龐大的try塊后勢必有大量的except塊,這時(shí)要判斷各個(gè)塊之間的邏輯關(guān)系,編寫程序會(huì)變得更加復(fù)雜。
3. 不要忽略捕捉到的異常: 程序應(yīng)該盡量修改異常,保證程序繼續(xù)運(yùn)行;不要將本層的異常傳給上一層去處理。
六、本節(jié)回顧
1. 你怎么理解異常處理機(jī)制?
2. Python異常處理的5個(gè)關(guān)鍵字是什么?(try\ except\else\finally\raise)
3. 如何使用raise引發(fā)異常?
4. 如何獲得異常的源頭和軌跡?
5.異常處理的原則有哪些?