異常處理在任何一門(mén)編程語(yǔ)言里都是值得關(guān)注的一個(gè)話題,良好的異常處理可以讓你的程序更加健壯,清晰的錯(cuò)誤信息更能幫助你快速修復(fù)問(wèn)題。在Python中,和不分高級(jí)語(yǔ)言一樣,使用了try/except/finally語(yǔ)句塊來(lái)處理異常,如果你有其他編程語(yǔ)言的經(jīng)驗(yàn),實(shí)踐起來(lái)并不難。
什么是異常?
1.錯(cuò)誤
從軟件方面來(lái)說(shuō),錯(cuò)誤是語(yǔ)法或是邏輯上的。錯(cuò)誤是語(yǔ)法或是邏輯上的。
語(yǔ)法錯(cuò)誤指示軟件的結(jié)構(gòu)上有錯(cuò)誤,導(dǎo)致不能被解釋器解釋或編譯器無(wú)法編譯。這些些錯(cuò)誤必須在程序執(zhí)行前糾正。
當(dāng)程序的語(yǔ)法正確后,剩下的就是邏輯錯(cuò)誤了。邏輯錯(cuò)誤可能是由于不完整或是不合法的輸入所致;
在其它情況下,還可能是邏輯無(wú)法生成、計(jì)算、或是輸出結(jié)果需要的過(guò)程無(wú)法執(zhí)行。這些錯(cuò)誤通常分別被稱(chēng)為域錯(cuò)誤和范圍錯(cuò)誤。
當(dāng)python檢測(cè)到一個(gè)錯(cuò)誤時(shí),python解釋器就會(huì)指出當(dāng)前流已經(jīng)無(wú)法繼續(xù)執(zhí)行下去。這時(shí)候就出現(xiàn)了異常。
2.異常
對(duì)異常的最好描述是:它是因?yàn)槌绦虺霈F(xiàn)了錯(cuò)誤而在正常控制流以外采取的行為。
這個(gè)行為又分為兩個(gè)階段:首先是引起異常發(fā)生的錯(cuò)誤,然后是檢測(cè)(和采取可能的措施)階段。
第一階段是在發(fā)生了一個(gè)異常條件(有時(shí)候也叫做例外的條件)后發(fā)生的。
只要檢測(cè)到錯(cuò)誤并且意識(shí)到異常條件,解釋器就會(huì)發(fā)生一個(gè)異常。引發(fā)也可以叫做觸發(fā),拋出或者生成。解釋器通過(guò)它通知當(dāng)前控制流有錯(cuò)誤發(fā)生。
python也允許程序員自己引發(fā)異常。無(wú)論是python解釋器還是程序員引發(fā)的,異常就是錯(cuò)誤發(fā)生的信號(hào)。
當(dāng)前流將被打斷,用來(lái)處理這個(gè)錯(cuò)誤并采取相應(yīng)的操作。這就是第二階段。
對(duì)于異常的處理發(fā)生在第二階段,異常引發(fā)后,可以調(diào)用很多不同的操作。
可以是忽略錯(cuò)誤(記錄錯(cuò)誤但不采取任何措施,采取補(bǔ)救措施后終止程序。)或是減輕問(wèn)題的影響后設(shè)法繼續(xù)執(zhí)行程序。
所有的這些操作都代表一種繼續(xù),或是控制的分支。關(guān)鍵是程序員在錯(cuò)誤發(fā)生時(shí)可以指示程序如何執(zhí)行。
python用異常對(duì)象(exception object)來(lái)表示異常。遇到錯(cuò)誤后,會(huì)引發(fā)異常。
如果異常對(duì)象并未被處理或捕捉,程序就會(huì)用所謂的回溯(traceback)終止執(zhí)行
異常處理
捕捉異常可以使用try/except語(yǔ)句。
try/except語(yǔ)句用來(lái)檢測(cè)try語(yǔ)句塊中的錯(cuò)誤,從而讓except語(yǔ)句捕獲異常信息并處理。
如果你不想在異常發(fā)生時(shí)結(jié)束你的程序,只需在try里捕獲它。
語(yǔ)法:
以下為簡(jiǎn)單的try....except...else的語(yǔ)法:

Try的工作原理是,當(dāng)開(kāi)始一個(gè)try語(yǔ)句后,python就在當(dāng)前程序的上下文中作標(biāo)記,這樣當(dāng)異常出現(xiàn)時(shí)就可以回到這里,try子句先執(zhí)行,接下來(lái)會(huì)發(fā)生什么依賴(lài)于執(zhí)行時(shí)是否出現(xiàn)異常。
如果當(dāng)try后的語(yǔ)句執(zhí)行時(shí)發(fā)生異常,python就跳回到try并執(zhí)行第一個(gè)匹配該異常的except子句,異常處理完畢,控制流就通過(guò)整個(gè)try語(yǔ)句(除非在處理異常時(shí)又引發(fā)新的異常)。
如果在try后的語(yǔ)句里發(fā)生了異常,卻沒(méi)有匹配的except子句,異常將被遞交到上層的try,或者到程序的最上層(這樣將結(jié)束程序,并打印缺省的出錯(cuò)信息)。
如果在try子句執(zhí)行時(shí)沒(méi)有發(fā)生異常,python將執(zhí)行else語(yǔ)句后的語(yǔ)句(如果有else的話),然后控制流通過(guò)整個(gè)try語(yǔ)句。
使用except而不帶任何異常類(lèi)型
你可以不帶任何異常類(lèi)型使用except,如下實(shí)例:

以上方式try-except語(yǔ)句捕獲所有發(fā)生的異常。但這不是一個(gè)很好的方式,我們不能通過(guò)該程序識(shí)別出具體的異常信息。因?yàn)樗东@所有的異常。
使用except而帶多種異常類(lèi)型
你也可以使用相同的except語(yǔ)句來(lái)處理多個(gè)異常信息,如下所示:

try-finally 語(yǔ)句
try-finally 語(yǔ)句無(wú)論是否發(fā)生異常都將執(zhí)行最后的代碼。

當(dāng)在try塊中拋出一個(gè)異常,立即執(zhí)行finally塊代碼。
finally塊中的所有語(yǔ)句執(zhí)行后,異常被再次觸發(fā),并執(zhí)行except塊代碼。
參數(shù)的內(nèi)容不同于異常。
下面來(lái)看一個(gè)實(shí)例:

點(diǎn)擊查看大圖
總結(jié)如下:
except語(yǔ)句不是必須的,finally語(yǔ)句也不是必須的,但是二者必須要有一個(gè),否則就沒(méi)有try的意義了。
except語(yǔ)句可以有多個(gè),Python會(huì)按except語(yǔ)句的順序依次匹配你指定的異常,如果異常已經(jīng)處理就不會(huì)再進(jìn)入后面的except語(yǔ)句。
except語(yǔ)句可以以元組形式同時(shí)指定多個(gè)異常,參見(jiàn)實(shí)例代碼。
except語(yǔ)句后面如果不指定異常類(lèi)型,則默認(rèn)捕獲所有異常,你可以通過(guò)logging或者sys模塊獲取當(dāng)前異常。
如果要捕獲異常后要重復(fù)拋出,請(qǐng)使用raise,后面不要帶任何參數(shù)或信息。
不建議捕獲并拋出同一個(gè)異常,請(qǐng)考慮重構(gòu)你的代碼。
不建議在不清楚邏輯的情況下捕獲所有異常,有可能你隱藏了很?chē)?yán)重的問(wèn)題。
盡量使用內(nèi)置的異常處理語(yǔ)句來(lái) 替換try/except語(yǔ)句,比如with語(yǔ)句,getattr()方法。
經(jīng)驗(yàn)案例
傳遞異常 re-raise Exception捕捉到了異常,但是又想重新引發(fā)它(傳遞異常),使用不帶參數(shù)的raise語(yǔ)句即可:

在Python2中,為了保持異常的完整信息,那么你捕獲后再次拋出時(shí)千萬(wàn)不能在raise后面加上異常對(duì)象,否則你的trace信息就會(huì)從此處截?cái)唷R陨鲜亲詈?jiǎn)單的重新拋出異常的做法。
還有一些技巧可以考慮,比如拋出異常前對(duì)異常的信息進(jìn)行更新。

如果你有興趣了解更多,建議閱讀這篇博客。
http://www.ianbicking.org/blog/2007/09/re-raising-exceptions.html
Python3對(duì)重復(fù)傳遞異常有所改進(jìn),你可以自己嘗試一下,不過(guò)建議還是同上。
Exception 和 BaseException
當(dāng)我們要捕獲一個(gè)通用異常時(shí),應(yīng)該用Exception還是BaseException?我建議你還是看一下 官方文檔說(shuō)明,這兩個(gè)異常到底有啥區(qū)別呢? 請(qǐng)看它們之間的繼承關(guān)系。

從Exception的層級(jí)結(jié)構(gòu)來(lái)看,BaseException是最基礎(chǔ)的異常類(lèi),Exception繼承了它。BaseException除了包含所有的Exception外還包含了SystemExit,KeyboardInterrupt和GeneratorExit三個(gè)異常。
有此看來(lái)你的程序在捕獲所有異常時(shí)更應(yīng)該使用Exception而不是BaseException,因?yàn)榱硗馊齻€(gè)異常屬于更高級(jí)別的異常,合理的做法應(yīng)該是交給Python的解釋器處理。
except Exception as e和 except Exception, e
代碼示例如下:

在Python2的時(shí)代,你可以使用以上兩種寫(xiě)法中的任意一種。在Python3中你只能使用第一種寫(xiě)法,第二種寫(xiě)法被廢棄掉了。第一個(gè)種寫(xiě)法可讀性更好,而且為了程序的兼容性和后期移植的成本,請(qǐng)你也拋棄第二種寫(xiě)法。
raise “Exception string”
把字符串當(dāng)成異常拋出看上去是一個(gè)非常簡(jiǎn)潔的辦法,但其實(shí)是一個(gè)非常不好的習(xí)慣。

上面的語(yǔ)句如果拋出異常,那么會(huì)是這樣的:

這在Python2.4以前是可以接受的做法,但是沒(méi)有指定異常類(lèi)型有可能會(huì)讓下游沒(méi)辦法正確捕獲并處理這個(gè)異常,從而導(dǎo)致你的程序掛掉。簡(jiǎn)單說(shuō),這種寫(xiě)法是是封建時(shí)代的陋習(xí),應(yīng)該扔了。
使用內(nèi)置的語(yǔ)法范式代替try/except
Python 本身提供了很多的語(yǔ)法范式簡(jiǎn)化了異常的處理,比如for語(yǔ)句就處理的StopIteration異常,讓你很流暢地寫(xiě)出一個(gè)循環(huán)。
with語(yǔ)句在打開(kāi)文件后會(huì)自動(dòng)調(diào)用finally中的關(guān)閉文件操作。我們?cè)趯?xiě)Python代碼時(shí)應(yīng)該盡量避免在遇到這種情況時(shí)還使用try/except/finally的思維來(lái)處理。

再比如,當(dāng)我們需要訪問(wèn)一個(gè)不確定的屬性時(shí),有可能你會(huì)寫(xiě)出這樣的代碼:

其實(shí)你可以使用更簡(jiǎn)單的getattr()來(lái)達(dá)到你的目的。

最佳實(shí)踐
最佳實(shí)踐不限于編程語(yǔ)言,只是一些規(guī)則和填坑后的收獲。
1.只處理你知道的異常,避免捕獲所有異常然后吞掉它們。
2.拋出的異常應(yīng)該說(shuō)明原因,有時(shí)候你知道異常類(lèi)型也猜不出所以然的。
3.避免在catch語(yǔ)句塊中干一些沒(méi)意義的事情。
4.不要使用異常來(lái)控制流程,那樣你的程序會(huì)無(wú)比難懂和難維護(hù)。
5.如果有需要,切記使用finally來(lái)釋放資源。
6如果有需要,請(qǐng)不要忘記在處理異常后做清理工作或者回滾操作。
異常速查表

-
編程語(yǔ)言
+關(guān)注
關(guān)注
10文章
1964瀏覽量
39558 -
異常處理
+關(guān)注
關(guān)注
0文章
15瀏覽量
7470 -
python
+關(guān)注
關(guān)注
57文章
4876瀏覽量
90022
原文標(biāo)題:一文掌握 Python 異常處理的所有知識(shí)點(diǎn)
文章出處:【微信號(hào):magedu-Linux,微信公眾號(hào):馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
Python異常處理和單元測(cè)試簡(jiǎn)介
如何有效的處理空指針異常
python常見(jiàn)異常類(lèi)型
python如何主動(dòng)拋出異常和捕獲異常
基于案例推理的工作流異常處理研究
基于VxWorks的異常處理的研究和實(shí)現(xiàn)
java異常處理的設(shè)計(jì)與重構(gòu)
python如何捕獲異常和主動(dòng)拋出異常
替代try catch處理異常的優(yōu)雅方式
發(fā)電機(jī)安裝過(guò)程中轉(zhuǎn)軸絕緣異常異常的原因及處理方法
Python-模塊、包、異常
Python中的異常機(jī)制(一)
基于Python 異常的介紹以及異常處理的方法解析
評(píng)論