引言
我們用什么語(yǔ)言寫項(xiàng)目,其實(shí)都會(huì)出錯(cuò),錯(cuò)誤有很多種,有我們寫的代碼本身就有問(wèn)題,這個(gè)時(shí)候?qū)儆诰幾g錯(cuò)誤,有的代碼運(yùn)行的時(shí)候有問(wèn)題,這個(gè)屬于運(yùn)行時(shí)異常,python其實(shí)也有一套自己的異常處理機(jī)制。今天我們來(lái)學(xué)習(xí)一下。
解決錯(cuò)誤中的一種方式:返回錯(cuò)誤碼
返回錯(cuò)誤碼其實(shí)是程序員排除錯(cuò)誤的一種方式,他們往往會(huì)有一個(gè)錯(cuò)誤碼的字典,字典里面記錄了什么數(shù)字對(duì)應(yīng)著什么錯(cuò)誤;然后當(dāng)程序返回什么碼的時(shí)候,去再對(duì)照之前那個(gè)字典,就能夠知道什么錯(cuò)誤了。
但是錯(cuò)誤碼其實(shí)有一個(gè)局限性,就是當(dāng)我們返回的結(jié)果如果和錯(cuò)誤碼混合在一起,那么久不太好區(qū)分了。
為了避免這種問(wèn)題,我們常用try catch finally(java語(yǔ)言中)等方式進(jìn)行運(yùn)行時(shí)異常的捕獲。
當(dāng)然python也有類似的。
try...except...finally
我們先試用一下
try:
print('try...')
r=10/0
print('result:',r)
except ZeroDivisionError as e:
print('except:',e)
finally:
print('finally...')
print('END')
大家應(yīng)該能看懂大概意思,我們來(lái)看下實(shí)際輸出結(jié)果

當(dāng)我們認(rèn)為某些代碼可能會(huì)出錯(cuò)時(shí),就可以用try來(lái)運(yùn)行這段代碼
如果執(zhí)行出錯(cuò),則后續(xù)代碼不會(huì)繼續(xù)執(zhí)行,而是直接跳轉(zhuǎn)至except語(yǔ)句塊
執(zhí)行完except后,如果有finally語(yǔ)句塊,則執(zhí)行finally語(yǔ)句塊
至此,執(zhí)行完畢
ok,當(dāng)然,如果沒(méi)有錯(cuò)誤出現(xiàn),那么也不會(huì)執(zhí)行except代碼塊,如下如

(注意:如果想得到整數(shù),那么用"http://")
從上面的輸出結(jié)果,可以看到,不論異常有沒(méi)有finally一直都會(huì)執(zhí)行,就像java中的一樣
那么,如果發(fā)生了不同種的錯(cuò)誤,是不是可以使用多種except去處理呢?
答案是肯定的!
我們直接來(lái)看示例

這個(gè)例子中,我們用一個(gè) except 來(lái)捕獲 ValueError ,一個(gè) except 來(lái)捕獲 ZeroDivisionError
此外,如果沒(méi)有錯(cuò)誤發(fā)生的話,還可以在 except 后添加一個(gè) else: 去執(zhí)行沒(méi)有該錯(cuò)誤的處理
如下面例子所示

需要注意的是,并不是一個(gè) except 對(duì)應(yīng)一個(gè) else ,而是當(dāng)所有的 except 都沒(méi)處理的話,才會(huì)執(zhí)行唯一的 else ,下面的寫法是錯(cuò)的
錯(cuò)誤寫法

Python的錯(cuò)誤也是類,所以如果想捕獲某一大類的錯(cuò)誤,直接捕獲大類就好了
所有的錯(cuò)誤類,都繼承自 BaseException類
常見(jiàn)的錯(cuò)誤類型和繼承關(guān)系看這里:
https://docs.python.org/3/library/exceptions.html#exception-hierarchy
使用try...except捕獲錯(cuò)誤還有一個(gè)巨大的好處,就是可以跨越多層調(diào)用
請(qǐng)看下方示例:

由此我們發(fā)現(xiàn),有了這個(gè)跨越多層調(diào)用 except ,大大減少了復(fù)雜的 try except finally 代碼的使用
調(diào)用棧
如果出的錯(cuò)沒(méi)有處理,那么最終會(huì)被 python 解釋器捕獲,打印一個(gè)錯(cuò)誤信息,然后程序退出
我們來(lái)演示一下

出錯(cuò)的時(shí)候,一定要分析錯(cuò)誤的調(diào)用棧信息,才能定位錯(cuò)誤的位置
出了錯(cuò)之后要懂得找打錯(cuò)誤的位置,上面的例子錯(cuò)誤的位置就在第三行,10/s的位置,找到錯(cuò)誤的位置,確定錯(cuò)誤的類型,我們就能夠解決了
logging類
我們從上面的例子知道了,即使我們不進(jìn)行異常的捕獲,python編譯器也會(huì)對(duì)異常進(jìn)行處理,我們也能定位到異常的位置,那么還要異常處理干什么?
其實(shí)不然,python處理器雖然會(huì)處理異常,但是在處理的同時(shí),也將程序終止。
其實(shí)我們可以借助logging類去處理,logging類既會(huì)想python處理器一樣將錯(cuò)誤展示出來(lái),也不會(huì)終止程序,如下例所示

拋出錯(cuò)誤
需要明確的是:
錯(cuò)誤是一個(gè) class ,我們捕獲的錯(cuò)誤其實(shí)就是捕獲了該 class 的一個(gè)實(shí)例
所以說(shuō),錯(cuò)誤并不是憑空出現(xiàn)的,而是有意拋出的
python內(nèi)置的函數(shù)會(huì)拋出很多類型的錯(cuò)誤,當(dāng)然我們自己編寫的函數(shù)也可以拋出錯(cuò)誤。
如果要拋出錯(cuò)誤,我們可以根據(jù)需要,可以定義一個(gè)表示錯(cuò)誤的 class ,然后選擇好繼承關(guān)系,
最后用 raise 語(yǔ)句拋出一個(gè)錯(cuò)誤的實(shí)例。
比如,現(xiàn)在定義一個(gè)錯(cuò)誤,讓他繼承ValueError
class FooError(ValueError):
pass
然后在適當(dāng)?shù)牡胤綊伋?/p>
def foo(s):
if s==0:
raise FooError('Foo Error %s' %s)
return 10/s
這樣,當(dāng)我們調(diào)用foo函數(shù)時(shí),如果我們傳入
0
那么將會(huì)拋出FooError的錯(cuò)誤,并且打印
“Foo Error 0”
否則的話,則會(huì)直接返回 10/s 的返回值
我們來(lái)看一下真實(shí)的案例演示:


但是,我們只有在必要的時(shí)候才會(huì)定義我們自己的錯(cuò)誤類型,在定義錯(cuò)誤類型時(shí),一定要繼承對(duì)父類,否則會(huì)造成麻煩
下面再來(lái)看另外一中處理錯(cuò)誤的方式:重復(fù)拋出錯(cuò)誤

由上圖,我們可以看出,雖然已經(jīng)捕獲了異常:打印出了FooError!
但是又將之前的異常拋出,這是什么操作呢?
這個(gè)操作有的時(shí)候很有必要
有的情況下,由于當(dāng)前函數(shù)不知道怎樣去處理當(dāng)前遇到的錯(cuò)誤,那么最恰當(dāng)?shù)姆绞绞峭蠏仯岉攲诱{(diào)用者去處理。就好比像一個(gè)員工遇到了一個(gè)棘手的問(wèn)題不知道怎么處理,那么他最恰當(dāng)?shù)淖龇ㄊ墙唤o他的上司,如果他的上司也處理不好,那么就會(huì)一直往上拋,直到拋給公司的大老板,讓大老板去處理
注意用法
raise 如果直接寫的話,那么就是把錯(cuò)誤直接原封不動(dòng)的拋給上級(jí)
如果raise 后追加別的錯(cuò)誤類型的話,其實(shí)就完成了把錯(cuò)誤轉(zhuǎn)化成另外一個(gè)類型額操作了,如
try:
10/s
except ZeroDivisionError as e:
print('ZeroDivisionError')
raise ValueError('ValueError')
當(dāng)然了,這種轉(zhuǎn)化只要符合邏輯就可以,但是切忌瞎轉(zhuǎn)化。