異常處理
異常Exception
錯(cuò)誤 Error :錯(cuò)誤是可以避免的
邏輯錯(cuò)誤:算法寫(xiě)錯(cuò)了,加法寫(xiě)成了減法
筆誤:變量名寫(xiě)錯(cuò)了,語(yǔ)法錯(cuò)誤
函數(shù)或類(lèi)使用錯(cuò)誤,其實(shí)這也屬于邏輯錯(cuò)誤
異常 Exception :異常不可能避免
本意就是意外情況
這有個(gè)前提,沒(méi)有出現(xiàn)上面說(shuō)的錯(cuò)誤,也就是說(shuō)程序?qū)懙臎](méi)有問(wèn)題,但是在某些情況下,會(huì)出現(xiàn)一些意外,導(dǎo)致程序無(wú)法正常的執(zhí)行下去。
例如open函數(shù)操作一個(gè)文件,文件不存在,或者創(chuàng)建一個(gè)文件時(shí)已經(jīng)存在了,或者訪問(wèn)一個(gè)網(wǎng)絡(luò)文件,突然斷網(wǎng)了,這就是異常,是個(gè)意外的情況。
錯(cuò)誤和異常
在高級(jí)編程語(yǔ)言中,一般都有錯(cuò)誤和異常的概念,異常是可以捕獲,并被處理的,但是錯(cuò)誤是不能被捕獲的。
產(chǎn)生異常
產(chǎn)生:
- raise 語(yǔ)句顯式的拋出異常
- Python解釋器自己檢測(cè)到異常并引發(fā)它
程序會(huì)在異常拋出的地方中斷執(zhí)行,如果不捕獲,就會(huì)提前結(jié)束程序(其實(shí)是終止當(dāng)前線程的執(zhí)行)
raise語(yǔ)句
raise后什么都沒(méi)有,表示拋出最近一個(gè)被激活的異常,如果沒(méi)有,則拋類(lèi)型異常。這種方式很少用 。
raise后要求應(yīng)該是BaseException類(lèi)的子類(lèi)或?qū)嵗?,如果是?lèi),將被無(wú)參實(shí)例化。
異常類(lèi)及繼承層次
# Python異常的繼承
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
+-- RuntimeError
| +-- RecursionError
+-- MemoryError
+-- NameError
+-- StopIteration
+-- StopAsyncIteration
+-- ArithmeticError
| +-- FloatingPointError
| +-- OverflowError
| +-- ZeroDivisionError
+-- LookupError
| +-- IndexError
| +-- KeyError
+-- SyntaxError
+-- OSError
| +-- BlockingIOError
| +-- ChildProcessError
| +-- ConnectionError
| | +-- BrokenPipeError
| | +-- ConnectionAbortedError
| | +-- ConnectionRefusedError
| | +-- ConnectionResetError
| +-- FileExistsError
| +-- FileNotFoundError
| +-- InterruptedError
| +-- IsADirectoryError
| +-- NotADirectoryError
| +-- PermissionError
| +-- ProcessLookupError
| +-- TimeoutError
BaseException及子類(lèi)
BaseException
所有內(nèi)建異常類(lèi)的基類(lèi)是BaseException
SystemExit
sys.exit(n)函數(shù)引發(fā)的異常,異常不捕獲處理,就直接交給Python解釋器,解釋器退出。n=0,正常退出,n=1異常退出。
如果except語(yǔ)句捕獲了該異常,則繼續(xù)向后面執(zhí)行,如果沒(méi)有捕獲住該異常SystemExit,解釋器直接退出程序。
import sys
print('before')
sys.exit(1)
print('SysExit')
print('outer') # 不執(zhí)行
# 捕獲這個(gè)異常
import sys
try:
sys.exit(1)
except SystemExit: # 換成Exception
print('SysExit')
print('outer') # 執(zhí)行
KeyboardInterrupt
對(duì)應(yīng)的捕獲用戶中斷行為Ctrl + C
try:
import time
while True:
time.sleep(0.5)
pass
except KeyboardInterrupt:
print('ctl + c')
print('outer')
Exception及子類(lèi)
Exception是所有內(nèi)建的、非系統(tǒng)退出的異常的基類(lèi),自定義異常應(yīng)該繼承自它
SyntaxError 語(yǔ)法錯(cuò)誤
Python將這種錯(cuò)誤也歸到異常類(lèi)下面的Exception下的子類(lèi),但是這種錯(cuò)誤是不可捕獲的
ArithmeticError 所有算術(shù)計(jì)算引發(fā)的異常,其子類(lèi)有除零異常等
LookupError
使用映射的鍵或序列的索引無(wú)效時(shí)引發(fā)的異常的基類(lèi):IndexError, KeyError
自定義異常
從Exception繼承的類(lèi)
class MyException(Exception):
pass
try:
raise MyException()
except MyException: # 捕獲自定義異常
print('catch the exception')
未實(shí)現(xiàn)和未實(shí)現(xiàn)異常
print(type(NotImplemented))
print(type(NotImplementedError))
#<class 'NotImplementedType'>
#<class 'type'>
NotImplemented是個(gè)值,單值,是NotImplementedType的實(shí)例
NotImplementedError是類(lèi)型,是異常,返回type
異常的捕獲
try:
待捕獲異常的代碼塊
except [異常類(lèi)型]:
異常的處理代碼塊
使用了try...except語(yǔ)句塊捕捉到了這個(gè)異常,異常生成位置之后語(yǔ)句將不再執(zhí)行,轉(zhuǎn)而執(zhí)行對(duì)應(yīng)的except部分的語(yǔ)句,最后執(zhí)行try...except語(yǔ)句塊之外的語(yǔ)句。
except 后接異常類(lèi)型,用來(lái)捕獲指定類(lèi)型的異常,except可以捕獲多個(gè)異常。
捕獲規(guī)則
捕獲是從上到下依次比較,如果匹配,則執(zhí)行匹配的except語(yǔ)句塊
如果被一個(gè)except語(yǔ)句捕獲,其他except語(yǔ)句就不會(huì)再次捕獲了
如果沒(méi)有任何一個(gè)except語(yǔ)句捕獲到這個(gè)異常,則該異常向外拋出
捕獲的原則
從小到大,從具體到寬泛
被拋出的異常,應(yīng)該是異常的實(shí)例,使用as子句接收這個(gè)拋出的異常。
finally子句
finally
最終,即最后一定要執(zhí)行的,try...finally語(yǔ)句塊中,不管是否發(fā)生了異常,都要執(zhí)行finally的部分
finally中一般放置資源的清理、釋放工作的語(yǔ)句,也可以在finally中再次捕捉異常。
異常的傳遞
def foo1():
? return 1/0
def foo2():
? print('foo2 start')
? foo1()
? print('foo2 stop')
foo2()
foo2調(diào)用了foo1,foo1產(chǎn)生的異常,傳遞到了foo2中。
異常總是向外層拋出,如果外層沒(méi)有處理這個(gè)異常,就會(huì)繼續(xù)向外拋出
如果內(nèi)層捕獲并處理了異常,外部就不能捕獲到了
如果到了最外層還是沒(méi)有被處理,就會(huì)中斷異常所在的線程的執(zhí)行。注意整個(gè)程序結(jié)束的狀態(tài)返回值。
# 線程中測(cè)試異常
import threading
import time
def foo1():
return 1/0
def foo2():
time.sleep(3) # 3秒后拋出異常
print('foo2 start')
foo1()
print('foo2 stop')
t = threading.Thread(target=foo2)
t.start()
while True:
time.sleep(1)
print('Everything OK')
if t.is_alive():
print('alive')
else:
print('dead')
try嵌套
內(nèi)部捕獲不到異常,會(huì)向外層傳遞異常
但是如果內(nèi)層有finally且其中有return、break語(yǔ)句,則異常就不會(huì)繼續(xù)向外拋出:異常被壓制。
try:
try:
ret = 1 / 0
except KeyError as e:
print(e)
finally:
print('inner fin')
except:
print('outer catch')
finally:
print('outer fin')
#輸出
#inner fin
#outer catch
#outer fin
異常的捕獲的時(shí)機(jī)
1.立即捕獲
需要立即返回一個(gè)明確的結(jié)果
def parse_int(s):
try:
return int(s)
except:
return 0
print(parse_int('s'))
2.邊界捕獲
封裝產(chǎn)生了邊界
例如,寫(xiě)了一個(gè)模塊,用戶調(diào)用這個(gè)模塊的時(shí)候捕獲異常,模塊內(nèi)部不需要捕獲、處理異常,一旦內(nèi)部處理了,外
部調(diào)用者就無(wú)法感知了。
例如,open函數(shù),出現(xiàn)的異常交給調(diào)用者處理,文件存在了,就不用再創(chuàng)建了,看是否修改還是刪除
例如,自己寫(xiě)了一個(gè)類(lèi),使用了open函數(shù),但是出現(xiàn)了異常不知道如何處理,就繼續(xù)向外層拋出,一般來(lái)說(shuō)最外
層也是邊界,必須處理這個(gè)異常了,否則線程退出
else子句
try:
ret = 1 * 0
except ArithmeticError as e:
print(e)
else:
print('OK')
finally:
print('fin')
else子句
沒(méi)有任何異常發(fā)生,則執(zhí)行
總結(jié)
try:
<語(yǔ)句> #運(yùn)行別的代碼
except <異常類(lèi)>:
<語(yǔ)句> # 捕獲某種類(lèi)型的異常
except <異常類(lèi)> as <變量名>:
<語(yǔ)句> # 捕獲某種類(lèi)型的異常并獲得對(duì)象
else:
<語(yǔ)句> #如果沒(méi)有異常發(fā)生
finally:
<語(yǔ)句> #退出try時(shí)總會(huì)執(zhí)行
try的工作原理
1、如果try中語(yǔ)句執(zhí)行時(shí)發(fā)生異常,搜索except子句,并執(zhí)行第一個(gè)匹配該異常的except子句
2、如果try中語(yǔ)句執(zhí)行時(shí)發(fā)生異常,卻沒(méi)有匹配的except子句,異常將被遞交到外層的try,如果外層不處理這個(gè)異
常,異常將繼續(xù)向外層傳遞。如果都不處理該異常,則會(huì)傳遞到最外層,如果還沒(méi)有處理,就終止異常所在的線程
3、如果在try執(zhí)行時(shí)沒(méi)有發(fā)生異常,將執(zhí)行else子句中的語(yǔ)句
4、無(wú)論try中是否發(fā)生異常,finally子句最終都會(huì)執(zhí)行。
模塊化
Python中只有一種模塊對(duì)象類(lèi)型,但是為了模塊化組織模塊的便利,提供了“包”的概念。模塊module,指的是Python的源代碼文件。
包package,指的是模塊組織在一起的和包名同名的目錄及其相關(guān)文件。
導(dǎo)入語(yǔ)句
| 語(yǔ)句 | 含義 |
|---|---|
| import 模塊1,[模塊2....] | 完全導(dǎo)入 |
| import....as..... | 模塊別名 |
import語(yǔ)句
1、找到指定的模塊,加載和初始化它,生成模塊對(duì)象。找不到,拋出ImportError
2、在import所在的作用域的局部命名空間中,增加名稱(chēng)和上一步創(chuàng)建的對(duì)象關(guān)聯(lián)。
總結(jié)
導(dǎo)入頂級(jí)模塊,其名稱(chēng)會(huì)加入到本地名詞空間中,并綁定到其模塊對(duì)象。
導(dǎo)入非頂層模塊,只將其頂級(jí)模塊名稱(chēng)加入到本地名稱(chēng)空間中。導(dǎo)入的模塊必須使用完全限定名稱(chēng)來(lái)訪問(wèn)。
如果使用了as,as后的名稱(chēng)直接綁定到導(dǎo)入的模塊對(duì)象,并將該名稱(chēng)加入到本地名詞空間中。