一、異常介紹
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í)行流程如下:
-
try語句首先被執(zhí)行,如果執(zhí)行成功(沒有拋出異常),則繼續(xù)執(zhí)行else語句(如果有),try語句此時結(jié)束。最后執(zhí)行finally語句(如果有)。 - 如果
try語句執(zhí)行時拋出異常,則except語句根據(jù)異常類型進行匹配。如某個except語句最終匹配拋出的異常類型,則拋出的異常賦值給其后的變量var(如果有),對應(yīng)的exception_code被執(zhí)行。 - 如果
try拋出的異常沒有任何except語句進行匹配,則該異常繼續(xù)傳遞看是否有內(nèi)嵌的try語句對其進行處理。 - 上面格式中最后的
except語句沒有跟任何異常類型,則表示它將匹配關(guān)聯(lián)所有的異常類型。這種方式在調(diào)試和創(chuàng)建程序原型時較常用,但并不推薦(因為隱藏了異常包含的細節(jié))。 -
try語句中的else是可選的且并不常用,它只有在try語句執(zhí)行后未拋出任何異常的情況下才會執(zhí)行。 -
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: