Python 進階之錯誤捕獲與異常處理

一、異常介紹

Python 中的異常是一種由 raise 語句自動創(chuàng)建的對象。在異常對象生成之后,Python 程序不會再繼續(xù)執(zhí)行 raise 后面緊跟的語句或者操作異常對象本身,而是“搜索”用于處理該異常的某個特定的 handler 。
如果該 handler 被 Python 程序找到,則它可以關(guān)聯(lián)并訪問異常對象獲取更多的信息;
如果沒有找到與異常對應(yīng)的 handler,則程序終止并輸出錯誤信息。

PS: LBYL 與 EAFP
從理念上講,Python 傾向于在錯誤發(fā)生之后通過捕獲異常來處理程序錯誤。稱為 easier to ask forgiveness than permission (EAFP) 。
另外一種錯誤處理的方式則是盡可能地在錯誤發(fā)生之前檢查所有可能發(fā)生的情況,這種模式稱為 look before you leap (LBYL) 。

Python 提供多種不同類型的異常用以反映錯誤產(chǎn)生的原因和場景等。每種類型的異常實際上都是一個 Python 類,且其中大多數(shù)都繼承于 Exception 。對于用戶自定義的異常類,也最好作為 Exception 的子類來實現(xiàn)。

Python 異常觸發(fā)時通常會輸出以下內(nèi)容:

>>> alist = [1,2,3]
>>> alist[7]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

上面代碼中的 alist[7] 在請求列表中的項目時超出了列表原本的長度,因此觸發(fā)了 IndexError 異常。該異常被 Python 交互解釋器獲取并處理,最終輸出錯誤信息。

如果需要,其實也可以在代碼中通過 raise 語句手動觸發(fā)異常:

>>> raise IndexError("Just kidding")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: Just kidding

二、異常捕獲

能夠終止程序的運行并輸出錯誤信息并不是異常機制的關(guān)鍵所在。異常機制的特殊性在于,通過定義合適的用于處理特定異常的 handler,可以確保一般的異常情況能夠被 handler 捕捉,而不會直接導(dǎo)致程序運行失敗。
handler 可以輸出錯誤信息給用戶,或者嘗試修復(fù)問題,但是重點在于它不會終止程序。

捕獲異常的基本語法如下:

try:
    body
except exception_type1 as var1:
    exception_code1
except exception_type2 as var2:
    exception_code2
...
except:
    default_exception_code
else:
    else_body
finally:
    finally

其具體的執(zhí)行流程如下:

  1. try 語句首先被執(zhí)行,如果執(zhí)行成功(沒有拋出異常),則繼續(xù)執(zhí)行 else 語句(如果有),try 語句此時結(jié)束。最后執(zhí)行 finally 語句(如果有)。
  2. 如果 try 語句執(zhí)行時拋出異常,則 except 語句根據(jù)異常類型進行匹配。如某個 except 語句最終匹配拋出的異常類型,則拋出的異常賦值給其后的變量 var(如果有),對應(yīng)的 exception_code 被執(zhí)行。
  3. 如果 try 拋出的異常沒有任何 except 語句進行匹配,則該異常繼續(xù)傳遞看是否有內(nèi)嵌的 try 語句對其進行處理。
  4. 上面格式中最后的 except 語句沒有跟任何異常類型,則表示它將匹配關(guān)聯(lián)所有的異常類型。這種方式在調(diào)試和創(chuàng)建程序原型時較常用,但并不推薦(因為隱藏了異常包含的細節(jié))。
  5. try 語句中的 else 是可選的且并不常用,它只有在 try 語句執(zhí)行后未拋出任何異常的情況下才會執(zhí)行。
  6. finally 語句同樣是可選的,它在任何情況下最終都會執(zhí)行。即便 try 語句拋出異常且沒有任何 except 語句進行捕獲和處理,該異常也是在 finally 語句執(zhí)行后拋出給用戶。
    因此 finally 語句多用于一些“清理”任務(wù),比如讀寫硬盤后的關(guān)閉文件等。如:
try:
    infile = open(filename)
    data = infile.read()
finally:
    infile.close()

三、assert 語句

assert 語句是 raise 語句的一種特殊形式,其語法格式如下:
assert expression, argument

其含義為,如果 expression 表達式的執(zhí)行結(jié)果為 False 且系統(tǒng)變量 __debug__ 的值為 True,則拋出 AssertionError 異常和 argument變量(可選)。

系統(tǒng)變量 __debug__ 的值默認(rèn)為 True,可以在 Python 運行時添加 -O-OO 選項將該值改為 False。
因此可以在開發(fā)程序時加入 assert 語句進行調(diào)試,而程序發(fā)布時將其保留在代碼中也不會產(chǎn)生任何影響(只需保證 __debug__False)。

>>> x = (1, 2, 3)
>>> assert len(x) > 5, "len(x) not > 5"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: len(x) not > 5

四、代碼示例

不捕獲異常
while True:
    x = int(input('Enter the first number: '))
    y = int(input('Enter the second number: '))
    print(x / y)

運行效果(出錯后程序退出):

$ python exceptions.py
Enter the first number: 5
Enter the second number: 4
1.25
Enter the first number: 4
Enter the second number: 0
Traceback (most recent call last):
  File "exception.py", line 4, in <module>
    print(x / y)
ZeroDivisionError: division by zero
$
捕獲除數(shù)不為零異常
while True:
    try:
        x = int(input('Enter the first number: '))
        y = int(input('Enter the second number: '))
        print(x / y)
    except ZeroDivisionError:
        print('Division by zero is illegal')

運行效果(出錯后異常被捕獲,程序不退出):

$ python exceptions.py
Enter the first number: 4
Enter the second number: 0
Division by zero is illegal
Enter the first number: 5
Enter the second number: 4
1.25
Enter the first number:
捕獲多個異常
while True:
    try:
        x = int(input('Enter the first number: '))
        y = int(input('Enter the second number: '))
        print(x / y)
    except ZeroDivisionError:
        print('Division by zero is illegal')
    except ValueError:
        print("That wasn't a number, was it?")

運行效果:

$ python exceptions.py
Enter the first number: 4
Enter the second number: 0
Division by zero is illegal
Enter the first number: 4
Enter the second number: hello
That wasn't a number, was it?
Enter the first number: 5
Enter the second number: 4
1.25
Enter the first number:
捕獲所有異常(不推薦,隱藏了異常的細節(jié))
while True:
    try:
        x = int(input('Enter the first number: '))
        y = int(input('Enter the second number: '))
        print(x / y)
    except:
        print('Something wrong happened ...')

運行效果:

$ python exceptions.py
Enter the first number: 4
Enter the second number: 0
Something wrong happened ...
Enter the first number: 4
Enter the second number: hello
Something wrong happened ...
Enter the first number:

稍好一點的版本(輸出異常信息):

while True:
    try:
        x = int(input('Enter the first number: '))
        y = int(input('Enter the second number: '))
        print(x / y)
    except Exception as e:
        print('Invalid input:', e)

運行效果:

Enter the first number: 4
Enter the second number: 0
Invalid input: division by zero
Enter the first number: 4
Enter the second number: hello
Invalid input: invalid literal for int() with base 10: 'hello'
Enter the first number:

參考資料

Beginning Python 3rd
The Quick Python Book 3rd Edition

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容