一、異常處理的意義
1. 異常機(jī)制已經(jīng)成為衡量一門編程語言是否成熟的標(biāo)準(zhǔn)之一趟卸,使用異常處理機(jī)制的Python程序會有更好的容錯性。
2. 沒有人能夠保證自己寫的程序永遠(yuǎn)不會出錯,既是程序沒有錯誤谷饿,也不能保證用戶按你的意圖來輸入窄赋,另外還有系統(tǒng)的穩(wěn)定性傅联,計(jì)算硬件是否損壞秸苗,網(wǎng)絡(luò)掉線等諸多情況。
二恢暖、異常處理機(jī)制
1. 使用try...except捕獲異常
1)例如排监,通常在程序運(yùn)行時,用戶可以隨意輸入杰捂,程序不會因?yàn)橛脩舻妮斎氩缓戏ǘ蝗婚g退出舆床,而是向用戶提示輸入不合法,并請用戶再次輸入嫁佳。
那么我們就希望有一種強(qiáng)大的if塊來解決這個非法輸入的問題:
if 用戶輸入不合法:
? ? alert 輸入不合法
? ? go retry
else:
????#業(yè)務(wù)實(shí)現(xiàn)代碼
2)但是“用戶輸入不合法”這個條件怎么定義呢挨队?我們可以使用正則表達(dá)式與用戶的輸入進(jìn)行匹配。但現(xiàn)實(shí)中不合法的情況非常多蒿往,想讓程序一次處理所有的錯誤盛垦,我們可以將上面的偽代碼修改為:
if 一切正常:
? ? #業(yè)務(wù)實(shí)現(xiàn)代碼
就是:
? ? alert 輸入不合法
? ? goto retry
3) 但“一切正常”依然是很抽象的瓤漏,無法轉(zhuǎn)化成代碼腾夯。在這種情形下Python提供了一種假設(shè):如果程序可以順利運(yùn)行,那么就是“一切正呈叱洌”蝶俱。由此得出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)會自動生成一個異常對象饥漫,該異常對象被提交給Python解釋器榨呆,這個過程被稱為引發(fā)異常。
5)當(dāng)Python解釋器收到異常對象時趾浅,會尋找處理該異常對象的except塊愕提,如果找到合適的except塊馒稍,則把該過程稱為捕捉異常皿哨。如果Python解釋器找到不捕獲異常的except塊浅侨,則運(yùn)行時環(huán)境終止,Python解釋器也將退出证膨。
6)不管代碼塊是否處于try中如输,或except塊中,只要執(zhí)行該代碼時了現(xiàn)了異常央勒,系統(tǒng)總會自動生成一個Error對象不见。
7) try 可以有多個except塊,這是為了針對不同的異常類提供不同的異常處理方式崔步。當(dāng)系統(tǒng)發(fā)生不同意外情況時稳吮,系統(tǒng)會生成不同的異常對象。如果try被執(zhí)行一次井濒,灶似,則try后面只有一個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)行的程序所提供的第一個參數(shù)
b = int(sys.argv[2])# 代表當(dāng)前運(yùn)行的程序所提供的第二個參數(shù)
c = a/b
print("您輸入的兩個數(shù)相除的結(jié)果是:",c)
except IndexError:
print("索引錯誤:運(yùn)行程序時輸入的參數(shù)個數(shù)不夠")
except ValueError:
print("數(shù)值錯誤:程序只能接受整數(shù)參數(shù)")
except ArithmeticError:
print("算術(shù)錯誤")
except Exception:
print("未知異常")
3. 多異常捕獲:指一個Except塊可以捕獲多種類型的異常。例如:
>>> import sys
>>> try:
a = int(sys.argv[1])
b = int(sys.argv[2])
c = a/b
print("您輸入的兩個數(shù)相除的結(jié)果是:",c)
except (IndexError, ValueError, ArithmeticError):
print("程序發(fā)生了數(shù)組越界刽辙、格式錯誤窥岩、算術(shù)異常之一")
except: # 此處的省略也是合法的,一般放在最后
print("未知異常")
程序發(fā)生了數(shù)組越界扫倡、格式錯誤谦秧、算術(shù)異常之一
4. 訪問異常信息
1)如果程序需要在except塊中訪問異常對象的相關(guān)信息,則可以通過為異常對象聲明變量來實(shí)現(xiàn)撵溃。
2)所在的異常對象都包含如下幾個對象和方法:
args: 該屬性返回異常的錯誤編號和描述字符串
errno:該屬性返回異常的錯誤編號
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異常處理流程中還可添加一個else塊疚鲤,當(dāng)try塊沒有出現(xiàn)異常時,程序會執(zhí)行else塊缘挑。
例如:
>>> s = input("請輸入除數(shù):")
請輸入除數(shù):5
>>> try:
result = 20/int(s)
print('20除以%s的結(jié)果是:%g'%(s,result))
except ValueError:
print('值錯誤集歇,您必須輸入數(shù)值')
except ArithmeticError:
print('算術(shù)錯誤,您不能輸入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塊中的異常不會被except捕獲,該異常會傳給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)不會判斷這樣的異常,只能程序員來決定是否引發(fā)異常型奥,此時可以使用raise語句來完成這種自行引發(fā)的異常瞳收。
2. raise語句三種常見的用法:
1) 單獨(dú)一個raise,該語句引發(fā)當(dāng)前上下文中捕獲的異常桩引,或默認(rèn)引發(fā)RuntimeError異常缎讼。
2)raise后帶一個異常類,該語句引發(fā)指定的異常類坑匠。
3)raise后帶一個異常對象血崭,該語句引發(fā)制定的異常對象。
以上三種最終都是引發(fā)一個異常實(shí)例厘灼,每次只能引發(fā)一個異常實(shí)例夹纫。
3. 用戶引發(fā)異常的兩種方式:raise和except,例如:
>>> def main():
try: # 使用try...except來捕獲異常设凹,此時出現(xiàn)異常也不會傳給調(diào)用它的main()函數(shù)
mtd(3)
except Exception as e:
print('程序出現(xiàn)異常類是:',e)
mtd(3) #不使用try...except來捕獲異常舰讹,異常會傳播并導(dǎo)致程序中止
>>> def mtd(a):
if a>0:
raise ValueError("a的值大于0,不符合要求")
>>> main()
四、異常的傳播軌跡
1. 異常只要沒有被完全捕獲闪朱,異常就會從發(fā)生異常的函數(shù)或方法向外傳播月匣,首先傳給該函數(shù)或方法的調(diào)用者,然后奋姿,锄开,直到傳給Python解釋器,Python解釋器就會中止程序称诗,并打印異常傳播的軌跡信息萍悴。所以通常我們看到大段的異常信息,并不一定發(fā)生很多嚴(yán)重的問題,可能只是一個異常引發(fā)的癣诱。
2. Python專門提供trackback模塊來處理異常傳播的軌跡计维,兩種常用的方法是:
1)trackback.print_exc(): 將異常傳播軌跡輸出到控制臺或文件中。
2) format_exc():將異常傳播軌跡轉(zhuǎn)換成字符串撕予。
五鲫惶、異常處理規(guī)則
1.? 不要過度使用異常處理,注意以下兩點(diǎn):
1) 需要編寫錯誤處理代碼嗅蔬,而不是簡單的用異常來代替處理剑按。
2)? 不能用異常來代替流程控制疾就。
2.? 不要使用過度龐大的try塊:try塊越大澜术,發(fā)生異常的可能性就越大。而且在龐大的try塊后勢必有大量的except塊猬腰,這時要判斷各個塊之間的邏輯關(guān)系鸟废,編寫程序會變得更加復(fù)雜。
3. 不要忽略捕捉到的異常: 程序應(yīng)該盡量修改異常姑荷,保證程序繼續(xù)運(yùn)行盒延;不要將本層的異常傳給上一層去處理。
六鼠冕、本節(jié)回顧
1. 你怎么理解異常處理機(jī)制添寺?
2. Python異常處理的5個關(guān)鍵字是什么?(try\ except\else\finally\raise)
3. 如何使用raise引發(fā)異常懈费?
4. 如何獲得異常的源頭和軌跡计露?
5.異常處理的原則有哪些?