對于Python異常處理慎用“except:pass”建議
翻譯自StackOverflow中一個關(guān)于Python異常處理的問答。
問題:為什么“except:pass”是一個不好的編程習(xí)慣?
我時常在StackOverflow上看到有人評論關(guān)于except: pass的使用,他們都提到這是一個不好的Python編程習(xí)慣,應(yīng)該避免??晌蚁胫罏槭裁??有時候我并不在意出現(xiàn)的錯誤,而是只想讓我的程序繼續(xù)進(jìn)行下去。就像這樣:
try: something except: pass
為什么這么使用except:pass不好?這背后的原因是什么,是不是因為這樣我會放掉一些本該被處理的錯誤?還是這樣我會捕獲到所有類型的錯誤?
最佳回答:
正如你所猜測的那樣,這么做的確有兩個不好的地方。首先,因為沒有指定任何異常類型,所以會捕獲到任何類型的錯誤。其次,捕獲到錯誤之后只會簡單地讓它通過而不是采取必要的處理措施。
我接下來的解釋或許會有點長,所以將重點總結(jié)如下:
1. 不要將任意類型的錯誤作為捕獲對象。必須明確你想要捕獲的錯誤類型,并且寫明只捕獲它們。
2. 不要試圖簡單地敷衍錯誤處理動作。除非這么做是有目的的,但這通常都不太好。
那么接下來讓我們更深入一些:
不要將任意異常作為捕獲目標(biāo)
當(dāng)在代碼中的某個地方使用異常捕獲語句塊時,你通常知道這個地方可能會拋出異常,并且你也知道這個地方可能會發(fā)生什么樣的問題進(jìn)而拋出何種異常,一旦異常被拋出,你將捕獲到這個異常并使程序回到正軌上來。這就意味著你一定對這種異常有所準(zhǔn)備,并能夠在它發(fā)生的時候及時采取措施進(jìn)行處理。
舉個例子,你需要用戶輸入一個數(shù)字,并且使用int()函數(shù)將用戶輸入的字符串轉(zhuǎn)換為整數(shù)類型,這時候你一定會想到如果輸入的字符串并不是數(shù)字,那么就會發(fā)生值錯誤(ValueError)。如果真的發(fā)生了錯誤,那么你可以通過簡單的讓用戶重新輸入來讓程序回到正軌,所以捕獲值錯誤以及促使用戶重新輸入就是一個比較合理的處理策略。再舉一個例子,如果你想從一個文件中讀取配置信息,但正巧這個文件不存在。那么因為這是一個配置文件,如果它不存在你會返回一些默認(rèn)的配置選項,所以這個文件就不是這么必要了。在這個例子中,捕獲文件未找到錯誤(FileNotFoundError)以及返回默認(rèn)配置項則是一個比較合適的處理策略。通過以上兩個例子可以看到,我們都是在等待捕獲特定的錯誤,并且針對每種錯誤都有特定的處理策略。
然而,如果我們在這里捕獲所有的異常,那么為特定異常準(zhǔn)備的那些處理策略就會因為遇到非正常類型的異常而失效,這將會使得正常的程序流程中斷并且無法恢復(fù)。
讓我們還是舉配置文件的那個例子。正常的處理策略是如果發(fā)現(xiàn)文件并不存在,我們將使用默認(rèn)的配置項,并可能在稍后決定是否將當(dāng)前的配置項自動保存為配置文件(這樣的話下一次文件就肯定存在了)?,F(xiàn)在讓我們假定我們捕獲到了一個IsADirectoryError或是PermissionError錯誤,在這種情況下,我們可能不想讓程序繼續(xù)執(zhí)行,我們?nèi)匀荒軌蚴褂媚J(rèn)的配置參數(shù),但是隨后我們就不能保存文件了。也有可能用戶希望使用自定義的配置項,所以這樣的話就不能使用默認(rèn)配置項了。所以我們這時候可能需要立即告知用戶并停止當(dāng)前程序。也有可能我們并不想在這么一小塊代碼中做這么多的事情,而是讓應(yīng)用層面的部分去關(guān)心它,所以我們也可能讓這個錯誤浮到頂層,讓頂層的業(yè)務(wù)邏輯去處理。
在Python 2 idioms document文檔中也提到了一個簡單的例子:如果在我們的代碼中出現(xiàn)了一個簡單的拼寫錯誤而導(dǎo)致程序錯誤。在這種情況下因為我們捕獲所有的異常,所以我們將會捕獲到名稱錯誤(NameErrors)以及語法錯誤(SyntaxErrors)。兩者都是常見的錯誤,并且兩者都是不希望出現(xiàn)在我們最終代碼中的。但是因為我們什么異常都逮,當(dāng)異常發(fā)生時我們將無法區(qū)分具體的錯誤類型并且無法進(jìn)行調(diào)試。
但是也存在這樣一些并未預(yù)先準(zhǔn)備的危險異常,諸如系統(tǒng)錯誤(SystemError)就很少發(fā)生以至于我們根本沒有準(zhǔn)備;這些異常通常需要更復(fù)雜的處理操作,這些操作通??赡軙笪覀兺V巩?dāng)前的工作。
在任何情況下,通過局部代碼實現(xiàn)對所有異常的處理基本上都是不可能的,所以你應(yīng)該有針對性的去處理那些經(jīng)過準(zhǔn)備的特定異常。有些人曾建議至少應(yīng)該明確指明基本異常(Exception)這樣的不包含諸如系統(tǒng)退出(SystemExit)和鍵盤中斷(KeyboardInterrupt)這樣設(shè)計用來終止應(yīng)用程序的異常。但是我想說這樣還是不夠明確,并且我個人認(rèn)為只有在一個地方才能僅僅只捕獲Exception或是任何類型的異常,那就是一個單獨的,應(yīng)用程序?qū)用娴漠惓2东@器,并且這個捕獲器唯一的任務(wù)就是去捕獲任何可能出現(xiàn)的未經(jīng)準(zhǔn)備的漏網(wǎng)異常。這樣的話我們?nèi)匀荒軌虮A粢馔獍l(fā)生異常的相關(guān)信息作為進(jìn)一步的代碼擴展的依據(jù)(讓然如果我們能讓程序恢復(fù)的話),這樣下一次我們就能夠把這個異常在合適的地方顯式地指定出來或是指導(dǎo)我們撰寫測試用例以保證錯誤不再發(fā)生(當(dāng)然了,這一切還是要當(dāng)我們對特定異常有所準(zhǔn)備時,沒有準(zhǔn)備的異常還是會溜掉)。
在異常處理的邏輯中,不要什么都不做
當(dāng)顯式地捕獲到有限的幾個異常之后,很多時候我們的確不需要做什么特別的處理。這種情況下,except SomeSpecificException: pass這么做是可以的。但是大多數(shù)情況下,我們還是需要一些與錯誤恢復(fù)相關(guān)的代碼,例如重復(fù)嘗試的動作以及設(shè)置默認(rèn)值。
同時也考慮到其它情況,例如如果我們的代碼結(jié)構(gòu)已經(jīng)確定了必須不斷嘗試直到成功才能繼續(xù),那么什么也不做就已經(jīng)夠了。具體來說,我們需要用戶輸入一個數(shù)字,因為我們知道用戶可能不會按照我們設(shè)計的那樣做,所以我們會將這個部分放入一個循環(huán),比如像這樣:
def askForNumber ():
while True:
try:
return int(input('Please enter a number: '))
except ValueError:
pass
因為我們會不斷讓用戶輸入直到?jīng)]有異常拋出為止,這種情況下我們就不需要在except塊中做其他任何特別的操作,這樣就夠了。當(dāng)然了,有人會說你至少應(yīng)該讓用戶得到一些錯誤信息以明確他們?yōu)槭裁丛诖吮环磸?fù)地要求輸入。
在其他一些情況下,except塊中的passing語句顯示了我們并沒有真正的對異常做好準(zhǔn)備。除非是一些簡單的異常(諸如值錯誤ValueError或類型錯誤TypeError)我們都應(yīng)該做一些操作,原因也很明顯,避免簡單的passing。如果真沒什么可做的(如果你真的確定),那么考慮加一些解釋性的注釋在此;否則,請擴展except塊添加一些恢復(fù)性的代碼。
except: pass
最不能容忍的就是兩者的結(jié)合了。這意味著我們自愿捕獲任何異常(包括那些我們沒有準(zhǔn)備的)并且對它們視而不見。你應(yīng)該至少在日志中記錄一下這個錯誤并且向上提出來終止當(dāng)前程序(我就不信出現(xiàn)MemoryError你的程序依然能正常運行)。放過這些異常將會使程序在錯誤的軌道上繼續(xù)運行下去并且丟掉了關(guān)鍵的錯誤信息從而使得錯誤不易被發(fā)現(xiàn),特別是當(dāng)不是你親自遇到它的時候。
所以,底線是去捕獲那些經(jīng)過準(zhǔn)備的特定異常;其他發(fā)生的異常要么是等著你去修復(fù)的錯誤,要么是你無法處理的。當(dāng)真的沒有什么可做的時候放過某些特定異常是可以的,其他情況如果這么做就只能被認(rèn)為是怠工或偷懶了。你的確應(yīng)該去處理這些異常的。
相關(guān)文章
Python?Excel數(shù)據(jù)處理之xlrd/xlwt/xlutils模塊詳解
在復(fù)雜的Excel業(yè)務(wù)數(shù)據(jù)處理中,三兄弟扮演的角色缺一不可。如何能夠使用xlrd/xlwt/xlutils三個模塊來實現(xiàn)數(shù)據(jù)處理就是今天的內(nèi)容,希望對大家有所幫助2023-03-03
對python while循環(huán)和雙重循環(huán)的實例詳解
今天小編就為大家分享一篇對python while循環(huán)和雙重循環(huán)的實例詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-08-08
Django結(jié)合使用Scrapy爬取數(shù)據(jù)入庫的方法示例
這篇文章主要介紹了Django結(jié)合使用Scrapy爬取數(shù)據(jù)入庫的方法示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03

