一文掌握 Python 異常處理的所有知識(shí)點(diǎn)

異常處理在任何一門編程語言里都是值得關(guān)注的一個(gè)話題,良好的異常處理可以讓你的程序更加健壯,清晰的錯(cuò)誤信息更能幫助你快速修復(fù)問題泽示。在Python中,和部分高級(jí)語言一樣蜜氨,使用了try/except/finally語句塊來處理異常械筛,如果你有其他編程語言的經(jīng)驗(yàn),實(shí)踐起來并不難飒炎。

什么是異常埋哟?

1.錯(cuò)誤

從軟件方面來說,錯(cuò)誤是語法或是邏輯上的。錯(cuò)誤是語法或是邏輯上的赤赊。

語法錯(cuò)誤指示軟件的結(jié)構(gòu)上有錯(cuò)誤闯狱,導(dǎo)致不能被解釋器解釋或編譯器無法編譯。這些些錯(cuò)誤必須在程序執(zhí)行前糾正抛计。

當(dāng)程序的語法正確后哄孤,剩下的就是邏輯錯(cuò)誤了。邏輯錯(cuò)誤可能是由于不完整或是不合法的輸入所致吹截;

在其它情況下瘦陈,還可能是邏輯無法生成、計(jì)算波俄、或是輸出結(jié)果需要的過程無法執(zhí)行晨逝。這些錯(cuò)誤通常分別被稱為域錯(cuò)誤和范圍錯(cuò)誤。

當(dāng)python檢測(cè)到一個(gè)錯(cuò)誤時(shí),python解釋器就會(huì)指出當(dāng)前流已經(jīng)無法繼續(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ā)叔汁,拋出或者生成。解釋器通過它通知當(dāng)前控制流有錯(cuò)誤發(fā)生检碗。

python也允許程序員自己引發(fā)異常据块。無論是python解釋器還是程序員引發(fā)的,異常就是錯(cuò)誤發(fā)生的信號(hào)折剃。

當(dāng)前流將被打斷另假,用來處理這個(gè)錯(cuò)誤并采取相應(yīng)的操作。這就是第二階段怕犁。

對(duì)于異常的處理發(fā)生在第二階段边篮,異常引發(fā)后,可以調(diào)用很多不同的操作奏甫。

可以是忽略錯(cuò)誤(記錄錯(cuò)誤但不采取任何措施戈轿,采取補(bǔ)救措施后終止程序。)或是減輕問題的影響后設(shè)法繼續(xù)執(zhí)行程序阵子。

所有的這些操作都代表一種繼續(xù)思杯,或是控制的分支。關(guān)鍵是程序員在錯(cuò)誤發(fā)生時(shí)可以指示程序如何執(zhí)行挠进。

python用異常對(duì)象(exception object)來表示異常色乾。遇到錯(cuò)誤后誊册,會(huì)引發(fā)異常。

如果異常對(duì)象并未被處理或捕捉暖璧,程序就會(huì)用所謂的回溯(traceback)終止執(zhí)行

異常處理

捕捉異嘲盖樱可以使用try/except語句。

try/except語句用來檢測(cè)try語句塊中的錯(cuò)誤漆撞,從而讓except語句捕獲異常信息并處理殴泰。

如果你不想在異常發(fā)生時(shí)結(jié)束你的程序,只需在try里捕獲它浮驳。

語法:

以下為簡(jiǎn)單的try....except...else的語法:

try:
<語句>        #運(yùn)行別的代碼
except <名字>:
<語句>        #如果在try部份引發(fā)了'name'異常
except <名字>悍汛,<數(shù)據(jù)>:
<語句>        #如果引發(fā)了'name'異常,獲得附加的數(shù)據(jù)
else: 
<語句>        #如果沒有異常發(fā)生

Try的工作原理是至会,當(dāng)開始一個(gè)try語句后离咐,python就在當(dāng)前程序的上下文中作標(biāo)記,這樣當(dāng)異常出現(xiàn)時(shí)就可以回到這里奉件,try子句先執(zhí)行宵蛀,接下來會(huì)發(fā)生什么依賴于執(zhí)行時(shí)是否出現(xiàn)異常。

  1. 如果當(dāng)try后的語句執(zhí)行時(shí)發(fā)生異常县貌,python就跳回到try并執(zhí)行第一個(gè)匹配該異常的except子句术陶,異常處理完畢,控制流就通過整個(gè)try語句(除非在處理異常時(shí)又引發(fā)新的異常)煤痕。
  2. 如果在try后的語句里發(fā)生了異常梧宫,卻沒有匹配的except子句,異常將被遞交到上層的try摆碉,或者到程序的最上層(這樣將結(jié)束程序塘匣,并打印缺省的出錯(cuò)信息)。
  3. 如果在try子句執(zhí)行時(shí)沒有發(fā)生異常巷帝,python將執(zhí)行else語句后的語句(如果有else的話)忌卤,然后控制流通過整個(gè)try語句。

使用except而不帶任何異常類型

你可以不帶任何異常類型使用except楞泼,如下實(shí)例:

try:
正常的操作
......................
except:
發(fā)生異常則執(zhí)行此處代碼
......................
else:
沒有異常則執(zhí)行此處代碼

以上方式try-except語句捕獲所有發(fā)生的異常驰徊。但這不是一個(gè)很好的方式,我們不能通過該程序識(shí)別出具體的異常信息堕阔。因?yàn)樗东@所有的異常棍厂。

使用except而帶多種異常類型

你也可以使用相同的except語句來處理多個(gè)異常信息,如下所示:

try:
正常的操作
......................
except(Exception1[, Exception2[,...ExceptionN]]]):
發(fā)生以上多個(gè)異常中的一個(gè)印蔬,執(zhí)行這塊代碼
......................
else:
如果沒有異常執(zhí)行這塊代碼

try-finally 語句

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

try:
<語句>
finally:
<語句>    #退出try時(shí)總會(huì)執(zhí)行
raise

當(dāng)在try塊中拋出一個(gè)異常,立即執(zhí)行finally塊代碼。

finally塊中的所有語句執(zhí)行后例驹,異常被再次觸發(fā)捐韩,并執(zhí)行except塊代碼。

參數(shù)的內(nèi)容不同于異常鹃锈。

下面來看一個(gè)實(shí)例:

def div(a, b):
try:
print(a / b)
except ZeroDivisionError:
print("Error: b should not be 0 !!")
except Exception as e:
print("Unexpected Error: {}".format(e))
else:
print('Run into else only when everything goes well')
finally:
print('Always run into finally block.')

# tests
div(2, 0)
div(2, 'bad type')
div(1, 2)

# Mutiple exception in one line
try:
print(a / b)
except (ZeroDivisionError, TypeError) as e:
print(e)

# Except block is optional when there is finally
try:
open(database)
finally:
close(database)

# catch all errors and log it
try:
do_work()
except:    
# get detail from logging module
logging.exception('Exception caught!')

# get detail from sys.exc_info() method
error_type, error_value, trace_back = sys.exc_info()
print(error_value)
raise

總結(jié)如下:

  1. except語句不是必須的荤胁,finally語句也不是必須的,但是二者必須要有一個(gè)屎债,否則就沒有try的意義了仅政。
  2. except語句可以有多個(gè),Python會(huì)按except語句的順序依次匹配你指定的異常盆驹,如果異常已經(jīng)處理就不會(huì)再進(jìn)入后面的except語句圆丹。
  3. except語句可以以元組形式同時(shí)指定多個(gè)異常,參見實(shí)例代碼躯喇。
  4. except語句后面如果不指定異常類型辫封,則默認(rèn)捕獲所有異常,你可以通過logging或者sys模塊獲取當(dāng)前異常廉丽。
  5. 如果要捕獲異常后要重復(fù)拋出倦微,請(qǐng)使用raise,后面不要帶任何參數(shù)或信息正压。
  6. 不建議捕獲并拋出同一個(gè)異常欣福,請(qǐng)考慮重構(gòu)你的代碼。
  7. 不建議在不清楚邏輯的情況下捕獲所有異常焦履,有可能你隱藏了很嚴(yán)重的問題拓劝。
  8. 盡量使用內(nèi)置的異常處理語句來 替換try/except語句,比如with語句裁良,getattr()方法凿将。

經(jīng)驗(yàn)案例

傳遞異常 re-raise Exception
捕捉到了異常校套,但是又想重新引發(fā)它(傳遞異常)价脾,使用不帶參數(shù)的raise語句即可:

def f1():
print(1/0)
def f2():
try:
f1()
except Exception as e:
raise # don't raise e !!!
f2()

在Python2中,為了保持異常的完整信息笛匙,那么你捕獲后再次拋出時(shí)千萬不能在raise后面加上異常對(duì)象侨把,否則你的trace信息就會(huì)從此處截?cái)唷R陨鲜亲詈?jiǎn)單的重新拋出異常的做法妹孙。

還有一些技巧可以考慮秋柄,比如拋出異常前對(duì)異常的信息進(jìn)行更新。

def f2():
try:
f1()
except Exception as e:
e.args += ('more info',)
raise

Python3對(duì)重復(fù)傳遞異常有所改進(jìn)蠢正,你可以自己嘗試一下骇笔,不過建議還是同上。

Exception 和 BaseException

當(dāng)我們要捕獲一個(gè)通用異常時(shí),應(yīng)該用Exception還是BaseException笨触?我建議你還是看一下 官方文檔說明懦傍,這兩個(gè)異常到底有啥區(qū)別呢? 請(qǐng)看它們之間的繼承關(guān)系芦劣。

BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
+-- StopIteration...
+-- StandardError...
+-- Warning...

從Exception的層級(jí)結(jié)構(gòu)來看粗俱,BaseException是最基礎(chǔ)的異常類,Exception繼承了它虚吟。BaseException除了包含所有的Exception外還包含了SystemExit寸认,KeyboardInterrupt和GeneratorExit三個(gè)異常。

有此看來你的程序在捕獲所有異常時(shí)更應(yīng)該使用Exception而不是BaseException串慰,因?yàn)榱硗馊齻€(gè)異常屬于更高級(jí)別的異常偏塞,合理的做法應(yīng)該是交給Python的解釋器處理。

except Exception as e和 except Exception, e

代碼示例如下:

try:
do_something()
except NameError as e:  # should
pass
except KeyError, e:  # should not
pass

在Python2的時(shí)代邦鲫,你可以使用以上兩種寫法中的任意一種烛愧。在Python3中你只能使用第一種寫法,第二種寫法被廢棄掉了掂碱。第一個(gè)種寫法可讀性更好怜姿,而且為了程序的兼容性和后期移植的成本,請(qǐng)你也拋棄第二種寫法疼燥。

raise “Exception string”

把字符串當(dāng)成異常拋出看上去是一個(gè)非常簡(jiǎn)潔的辦法沧卢,但其實(shí)是一個(gè)非常不好的習(xí)慣。

if is_work_done():
pass
else:
raise "Work is not done!" # not cool

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

Traceback (most recent call last):
File "/demo/exception_hanlding.py", line 48, in <module>
raise "Work is not done!"
TypeError: exceptions must be old-style classes or derived from BaseException, not str

這在Python2.4以前是可以接受的做法但狭,但是沒有指定異常類型有可能會(huì)讓下游沒辦法正確捕獲并處理這個(gè)異常,從而導(dǎo)致你的程序掛掉撬即。簡(jiǎn)單說立磁,這種寫法是是封建時(shí)代的陋習(xí),應(yīng)該扔了剥槐。

使用內(nèi)置的語法范式代替try/except

Python 本身提供了很多的語法范式簡(jiǎn)化了異常的處理唱歧,比如for語句就處理的StopIteration異常,讓你很流暢地寫出一個(gè)循環(huán)粒竖。

with語句在打開文件后會(huì)自動(dòng)調(diào)用finally中的關(guān)閉文件操作颅崩。我們?cè)趯慞ython代碼時(shí)應(yīng)該盡量避免在遇到這種情況時(shí)還使用try/except/finally的思維來處理。

# should not
try:
f = open(a_file)
do_something(f)
finally:
f.close()
# should 
with open(a_file) as f:
do_something(f)

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

try:
test = Test()
name = test.name  # not sure if we can get its name
except AttributeError:
name = 'default'

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

最佳實(shí)踐

最佳實(shí)踐不限于編程語言朽砰,只是一些規(guī)則和填坑后的收獲尖滚。

1.只處理你知道的異常捕獲所異常然后吞掉它們喉刘。

2.拋出的異常應(yīng)該說明原因,有時(shí)候你知道異常類型也猜不出所以然的漆弄。

3.避免在catch語句塊中干一些沒意義的事情饱搏。

4.不要使用異常來控制流程,那樣你的程序會(huì)無比難懂和難維護(hù)置逻。

5.如果有需要推沸,切記使用finally來釋放資源。

6如果有需要券坞,請(qǐng)不要忘記在處理異常后做清理工作或者回滾操作鬓催。

速查表

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市恨锚,隨后出現(xiàn)的幾起案子宇驾,更是在濱河造成了極大的恐慌,老刑警劉巖猴伶,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件课舍,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡他挎,警方通過查閱死者的電腦和手機(jī)筝尾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來办桨,“玉大人筹淫,你說我怎么就攤上這事∧刈玻” “怎么了损姜?”我有些...
    開封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)殊霞。 經(jīng)常有香客問我摧阅,道長(zhǎng),這世上最難降的妖魔是什么绷蹲? 我笑而不...
    開封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任棒卷,我火速辦了婚禮,結(jié)果婚禮上瘸右,老公的妹妹穿的比我還像新娘娇跟。我一直安慰自己岩齿,他們只是感情好太颤,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著盹沈,像睡著了一般龄章。 火紅的嫁衣襯著肌膚如雪吃谣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天做裙,我揣著相機(jī)與錄音岗憋,去河邊找鬼。 笑死锚贱,一個(gè)胖子當(dāng)著我的面吹牛仔戈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播拧廊,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼监徘,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了吧碾?” 一聲冷哼從身側(cè)響起凰盔,我...
    開封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎倦春,沒想到半個(gè)月后户敬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡睁本,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年尿庐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呢堰。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡屁倔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出暮胧,到底是詐尸還是另有隱情锐借,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布往衷,位于F島的核電站钞翔,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏席舍。R本人自食惡果不足惜布轿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望来颤。 院中可真熱鬧汰扭,春花似錦、人聲如沸福铅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽滑黔。三九已至笆包,卻和暖如春环揽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背庵佣。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來泰國打工歉胶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人巴粪。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓通今,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親肛根。 傳聞我的和親對(duì)象是個(gè)殘疾皇子衡创,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容