本節(jié)內(nèi)容如下:
- 什么是異常复旬,對異常的解釋和描述,口語描述和專業(yè)術(shù)語的聯(lián)系
- 代碼中出現(xiàn)錯(cuò)誤的處理手段
- 異常處理方式
- 什么樣的情況算異常
- 捕獲異潮谙眩【try-except-else-finally】
- 拋出異痴颍【raise】
- 常見異常
1. 什么是異常
我們程序在開發(fā)過程中,總會遇到各種各樣的一些問題闽巩,有些是由于拼寫担汤、配置崭歧、選項(xiàng)等等各種引起的程序錯(cuò)誤,有些是由于程序功能處理邏輯不完善引起的漏洞叔营,這些統(tǒng)稱為我們程序中的異常
所謂異常:就是不正常的情況所宰,錯(cuò)誤和漏洞都是不正常的情況歧匈,異常情況有時(shí)候也會稱呼為BUG件炉,也就是缺陷矮湘、漏洞的意思缅阳,程序執(zhí)行過程中出現(xiàn)異常會影響程序的正常執(zhí)行。
python中內(nèi)置了一整套完善的異常處理機(jī)制秀撇,可以讓開發(fā)人員快速針對出現(xiàn)問題的代碼進(jìn)行完善和處理向族。
我們針對python可能遇到的不同的異常情況件相,一般會做如下處理:
- 如果是拼寫、配置等等引起的錯(cuò)誤泛范,根據(jù)出錯(cuò)信息進(jìn)行排查錯(cuò)誤出現(xiàn)的位置進(jìn)行解決
- 如果是程序設(shè)計(jì)不完善引起的漏洞罢荡,根據(jù)漏洞的情況進(jìn)行設(shè)計(jì)處理漏洞的邏輯区赵;
切記:合理的處理BUG也是程序設(shè)計(jì)開發(fā)的一部分
2. 錯(cuò)誤處理
錯(cuò)誤的出現(xiàn),在程序中一般會有兩種表現(xiàn)从媚,一種是拼寫錯(cuò)誤拜效,一種是程序執(zhí)行過程中出現(xiàn)的錯(cuò)誤各谚,這樣兩種不同的錯(cuò)誤應(yīng)該怎么進(jìn)行追蹤和處理呢昌渤?
2.1. 拼寫錯(cuò)誤
常規(guī)情況下,拼寫錯(cuò)誤只是在簡單的記事本等環(huán)境下進(jìn)行開發(fā)時(shí)般眉,容易手誤產(chǎn)生拼寫錯(cuò)誤甸赃;當(dāng)前開發(fā)環(huán)境下冗酿,我們經(jīng)常使用一些半自動(dòng)化的IDE開發(fā)工具裁替,如pycharm等等,可以進(jìn)行簡單的程序關(guān)鍵字的拼寫檢查以及程序結(jié)構(gòu)的檢查襟沮,把一些簡單的拼寫問題掐死在萌芽之中
程序設(shè)計(jì)開發(fā)的學(xué)習(xí)需要經(jīng)歷一個(gè)過程臣嚣,建議開始的基礎(chǔ)部分使用超級記事本進(jìn)行開發(fā),如editplus淹父、ultraedit暑认、sublime等等大审,對于基礎(chǔ)的掌握會有一個(gè)非常不錯(cuò)的提升作用徒扶;進(jìn)入后續(xù)的企業(yè)級項(xiàng)目開發(fā)階段之后可以使用高級開發(fā)工具來提升我們的開發(fā)效率,如Pycharm导坟、eclipse等等惫周。
2.2. 程序運(yùn)行時(shí)錯(cuò)誤
程序運(yùn)行過程中康栈,也會出現(xiàn)各種各樣的錯(cuò)誤啥么,對于錯(cuò)誤的出現(xiàn)和提示信息必須有一個(gè)比較明確的掌握饥臂,才能在后續(xù)的程序開發(fā)中快速的開發(fā)并且修復(fù)問題,這里就會出現(xiàn)兩個(gè)步驟
- 確定問題及問題出現(xiàn)的代碼行
- 后續(xù)的問題處理【參考后面的異常處理】
首先我們必須查詢問題出現(xiàn)的錯(cuò)誤提示信息,觀察如下代碼
# 程序測試
class Person(object):
# 通過__slots__屬性定義可以擴(kuò)展的成員屬性名稱
__slots__ = ("__name", "address", "age", "gender", "email")
# 初始化方法
def __init__(self, name):
self.__names = name
# 屬性的set/get方法
def get_name(self):
return self.__name
def set_name(self, name):
self.__name = name
# 創(chuàng)建對象
p = Person("tom")
print(p.get_name())
這里我們使用的開發(fā)工具是PyCharm囚戚,代碼開發(fā)過程中驰坊,必須時(shí)刻觀察我們的編輯工具是否出現(xiàn)錯(cuò)誤提示拳芙,如果出現(xiàn)錯(cuò)誤提示就是關(guān)鍵字拼寫問題或者程序結(jié)構(gòu)設(shè)計(jì)問題,需要及時(shí)修改分飞;上面的代碼開發(fā)工具沒有報(bào)錯(cuò)譬猫,那就直接運(yùn)行代碼羡疗,出現(xiàn)如下結(jié)果:
Traceback (most recent call last):
File "D:/resp_work/PY_WORK/備課/模塊化開發(fā)/demo04/demo10.py", line 18, in <module>
p = Person("tom")
File "D:/resp_work/PY_WORK/備課/模塊化開發(fā)/demo04/demo10.py", line 7, in __init__
self.__names = name
AttributeError: 'Person' object has no attribute '_Person__names'
運(yùn)行結(jié)果中出現(xiàn)了錯(cuò)誤叨恨,錯(cuò)誤的名稱是AttributeError
痒钝,錯(cuò)誤的提示是'Person' object has no attribute '_Person__names'
,簡單翻譯過來就是在Person對象中沒有屬性_Person__names
僅僅依靠這樣的錯(cuò)誤提示,我們已經(jīng)了解到站宗,可能是我們對象的屬性操作過程中出現(xiàn)了什么錯(cuò)誤梢灭,到底出現(xiàn)了什么錯(cuò)誤呢蒸其?繼續(xù)觀察上面的錯(cuò)誤代碼:
從錯(cuò)誤的第一行代碼
Traceback (most recent call last):
這行代碼的意思是跟蹤錯(cuò)誤的出現(xiàn)的過程摸袁,查看跟蹤提示信息下面的第一行錯(cuò)誤提示:
File "D:/resp_work/PY_WORK/備課/模塊化開發(fā)/demo04/demo10.py", line 18, in <module>
p = Person("tom")
首先在文件D:/resp_work/PY_WORK/備課/模塊化開發(fā)/demo04/demo10.py
的第8行出現(xiàn)了錯(cuò)誤靠汁,錯(cuò)誤代碼是p = Person("tom")
蝶怔,這里是錯(cuò)誤開始的地方,明顯這里的代碼沒有什么錯(cuò)誤澳叉,那就接著往下看
File "D:/resp_work/PY_WORK/備課/模塊化開發(fā)/demo04/demo10.py", line 7, in __init__
self.__names = name
在文件D:/resp_work/PY_WORK/備課/模塊化開發(fā)/demo04/demo10.py
的第7行line 7
出現(xiàn)的錯(cuò)誤成洗,主要代碼是self.__names = name
瓶殃,看到這里,我們已經(jīng)明確误证,是在我們程序的__init__(self, name)
初始化方法中愈捅,寫錯(cuò)了我們的屬性名稱慈鸠,屬性名稱本意設(shè)置的是__name
但是錯(cuò)誤寫成了__names
青团,修改代碼如下
# 程序測試
class Person(object):
# 通過__slots__屬性定義可以擴(kuò)展的成員屬性名稱
__slots__ = ("__name", "address", "age", "gender", "email")
# 初始化方法
def __init__(self, name):
self.__name = name
# 屬性的set/get方法
def get_name(self):
return self.__name
def set_name(self, name):
self.__name = name
# 創(chuàng)建對象
p = Person("tom")
print(p.get_name())
執(zhí)行代碼督笆,出現(xiàn)如下結(jié)果:
tom
說明錯(cuò)誤正常處理了娃肿。
解決程序中遇到錯(cuò)誤的核心操作
核心操作其實(shí)就是定位錯(cuò)誤出現(xiàn)的行號,然后根據(jù)對代碼執(zhí)行前后的簡單分析來定位出現(xiàn)錯(cuò)誤的地方凭豪,簡單的錯(cuò)誤就可以直接修復(fù)嫂伞;當(dāng)然拯钻,某些情況下如果出現(xiàn)運(yùn)行過程中可能會出現(xiàn)的錯(cuò)誤说庭,就是程序中的異常了刊驴,對于異常的處理捆憎,請參考后面的異常處理部分。
3. 異常處理
所謂異常致份,是程序執(zhí)行過程中氮块,出現(xiàn)了不正常的情況影響了整個(gè)程序的正常執(zhí)行
所謂處理異常诡宗,就是先通過指定的條件捕獲異常塔沃,捕獲到異常之后進(jìn)行后續(xù)的處理蛀柴,以正常的情況提示并處理發(fā)生的異常鸽疾,讓程序正常的執(zhí)行的過程
python中出現(xiàn)的所有的異常,都是直接或者間接繼承自BaseException這個(gè)類的
3.1. 代碼中什么樣的情況是異常冒窍?
python提供了一套try-except-finally的異常處理代碼塊超燃,用于針對可能出現(xiàn)問題的代碼進(jìn)行容錯(cuò)和處理
異常處理的語法結(jié)構(gòu)如下:
try:
<正常要執(zhí)行的代碼語句意乓,執(zhí)行過程中可能會出現(xiàn)異常>
except <異常名稱>:
<對應(yīng)異常的處理代碼>
else:
<如果沒有異常届良,執(zhí)行的后續(xù)代碼>
finally:
<無論是否出現(xiàn)異常圣猎,最終都會執(zhí)行的代碼>
接下來送悔,觀察下面這段代碼的設(shè)計(jì)和執(zhí)行過程,你能發(fā)現(xiàn)問題出現(xiàn)在哪里嗎屋灌?
# 常規(guī)情況下的幾行代碼:計(jì)算兩個(gè)數(shù)的加法運(yùn)算
def add():
num1 = int(input("請輸入第一個(gè)數(shù)字:"))
num2 = int(input("請輸入第二個(gè)數(shù)字:"))
num3 = num1 + num2
print("兩個(gè)數(shù)字計(jì)算的結(jié)果是:" + str(num3))
# 調(diào)用函數(shù)開始計(jì)算
add()
# 執(zhí)行結(jié)果
~請輸入第一個(gè)數(shù)字:12
~請輸入第二個(gè)數(shù)字:10
~兩個(gè)數(shù)字計(jì)算的結(jié)果是:22
上述功能的程序設(shè)計(jì)時(shí)共郭,已經(jīng)考慮了諸多的問題疾呻,如用戶輸入的數(shù)據(jù)應(yīng)該是字符串岸蜗,代碼中通過int()方法進(jìn)行了強(qiáng)制類型轉(zhuǎn)換散吵,在最后輸出數(shù)據(jù)的時(shí)候矾睦,由于num3是數(shù)值,數(shù)值和字符串不能直接用符號+連接枚冗,所以對num3又通過str()函數(shù)強(qiáng)制轉(zhuǎn)換成了字符串赁温。正常情況下股囊,程序沒有任何問題稚疹。
但是上述程序的缺陷并非正常流程下,而是如果用戶在應(yīng)該輸入數(shù)字的情況下怪嫌,輸入了字母或者其他的非數(shù)字字符岩灭,程序就出現(xiàn)錯(cuò)誤了噪径,這個(gè)才是我們要解決的程序的BUG
>>> add()
請輸入第一個(gè)數(shù)字:ab
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in add
ValueError: invalid literal for int() with base 10: 'ab'
我們對上述函數(shù)進(jìn)行如下改造:
# 常規(guī)情況下的幾行代碼:計(jì)算兩個(gè)數(shù)的加法運(yùn)算
def add():
try:
num1 = int(input("請輸入第一個(gè)數(shù)字:"))
num2 = int(input("請輸入第二個(gè)數(shù)字:"))
num3 = num1 + num2
print("兩個(gè)數(shù)字計(jì)算的結(jié)果是:" + str(num3))
except:
print("您輸入的數(shù)值非法熄云,只能輸入整數(shù)")
# 調(diào)用函數(shù)開始計(jì)算:執(zhí)行過程如下
>>> add()
請輸入第一個(gè)數(shù)字:12
請輸入第二個(gè)數(shù)字:13
兩個(gè)數(shù)字的和:25
>>> add()
請輸入第一個(gè)數(shù)字:ab
您輸入了非法的非數(shù)字字符
可以看到缴允,上面通過添加try-except這樣的一個(gè)代碼塊练般,完美的解決了我們出現(xiàn)的錯(cuò)誤薄料,不至于讓錯(cuò)誤導(dǎo)致程序的崩潰
3.2. 異常處理的方式1——捕獲異常
異常處理泵琳,python中是通過try-except語句代碼塊來執(zhí)行處理的
try-except語句代碼塊處理異常通常有這樣幾種方式
- 使用try-except直接包含并處理所有異常
執(zhí)行代碼如下
try:
n = input("請輸入數(shù)字:")
num1 = int(n)
print("您輸入了數(shù)字:" + str(num1))
except:
print("出現(xiàn)異常获列,處理異常")
print("程序繼續(xù)執(zhí)行击孩,代碼執(zhí)行完成巩梢!")
>![使用try-except直接包含并處理所有異常](http://upload-images.jianshu.io/upload_images/5988045-aa0fafc390a2a4f5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
2. 使用try-except-except-except嵌套處理指定的多個(gè)異常
> ```
def add():
try:
n = input("請輸入數(shù)字:")
num1 = int(n) # 可能出現(xiàn)異常 ValueError
print("您輸入的數(shù)字是:" + num1) # 可能出現(xiàn)異常TypeError
except ValueError as e: # 處理指定的ValueError異常
print("輸入的數(shù)據(jù)不是數(shù)字")
except TypeError as e: # 處理指定的TypeError異常
print("整數(shù)不能喝數(shù)字拼接")
# 調(diào)用執(zhí)行
add()
- 使用try-except-except-else處理異常并執(zhí)行else代碼塊
我們通過將可能出現(xiàn)異常的代碼包含在try語句塊中鞠抑,如果程序執(zhí)行正常搁拙,就執(zhí)行后續(xù)的代碼慨蓝,可以將后續(xù)的代碼放在else中執(zhí)行
# 編寫記錄用戶輸入的函數(shù)
def add():
try:
n = input("請輸入數(shù)字:")
num1 = int(n)
except:
print("輸入的數(shù)據(jù)不是數(shù)字")
else:
print("您輸入的數(shù)字是:" + str(num1))
add()
- 使用try-except-except-finally處理異常并在finally中進(jìn)行后續(xù)處理
某些情況下弧满,程序在操作的過程中庭呜,需要使用一定的資源,如打開文件讀取或者向文件中寫入數(shù)據(jù)扶关,一旦操作完成节槐,需要關(guān)閉和文件的鏈接釋放資源铜异。
此時(shí)的流程就是:打開文件->讀取/寫入數(shù)據(jù)文件->關(guān)閉文件
在讀取/寫入數(shù)據(jù)到文件時(shí)揍庄,可能會出現(xiàn)異常蚂子,此時(shí)的要求時(shí)缭黔,不論是否出現(xiàn)異常试浙,最后的關(guān)閉文件的操作必須執(zhí)行田巴。
# 操作文件的函數(shù)
def readFile():
try:
# 打開文件
f = open("d:/test.txt")
# 寫入內(nèi)容
f.write("這是要寫到文件中的內(nèi)容")
except:
print("文件讀寫錯(cuò)誤")
finally:
# 關(guān)閉文件
f.close()
# 執(zhí)行函數(shù)
readFile()
3.2. 異常處理的方式2——拋出異常
某些情況下抄伍,我們捕獲到異常信息管宵,如果只是簡單的進(jìn)行處理箩朴,對后續(xù)的程序可能會造成一定的困擾炸庞,舉一個(gè)簡單的操作案例:老板讓員工老李去采購一批辦公用品
老板boss.py,讓員工老李Emp.py查牌,采購一批辦公用品
員工老李去采購辦公用品纸颜,結(jié)果出現(xiàn)異常情況胁孙,店面關(guān)門了浊洞;此時(shí)老李如果將這個(gè)異常自行處理了法希,就沒有結(jié)果了苫亦。老板那里根本不知道老李發(fā)生了什么狀況怨咪,最終功能沒有完成的同時(shí)老板boss.py模塊也沒有得到任何結(jié)果屋剑。
結(jié)果就是~程序出現(xiàn)了BUG,老李遺憾的離職了..
換一種思路
老板boss.py诗眨,讓員工老李Emp.py唉匾,采購一批辦公用品
員工老李去采購辦公用品,結(jié)果出現(xiàn)異常情況匠楚,店面關(guān)門了巍膘;此時(shí)老李將異常信息自行簡單處理了一下,同時(shí)拋出異常信息匯報(bào)給老板:店面關(guān)門~可以做其他準(zhǔn)備了芋簿;老板接收到老李拋給自己的異常信息,臨時(shí)調(diào)整計(jì)劃与斤;最后功能完成了肪康,老李升職了。
這時(shí)候我們必須得明確:異沉么可以捕獲進(jìn)行處理磷支,適當(dāng)?shù)臅r(shí)候異常也需要拋出給調(diào)用者處理
請觀察我們之前寫過的如下代碼:
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# 異常的拋出,首先要捕獲到異常食寡,將難以理解的異常
# 轉(zhuǎn)換成比較容易理解的異常拋出給調(diào)用者
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# 定義一個(gè)輸入數(shù)字的函數(shù)
def add():
try:
n = input("請輸入一個(gè)數(shù)字:")
num = int(n)
except:
# 如果出現(xiàn)異常雾狈,將ValueError異常轉(zhuǎn)換成更加容易理解的異常
raise ValueError("這里需要一個(gè)數(shù)字,您輸入了非數(shù)字字符")
add()
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# 轉(zhuǎn)換之前拋出的異常
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
請輸入一個(gè)數(shù)字:a
Traceback (most recent call last):
File "D:/resp_work/PY_WORK/備課/模塊化開發(fā)/demo05/demo02.py", line 8, in <module>
add()
File "D:/resp_work/PY_WORK/備課/模塊化開發(fā)/demo05/demo02.py", line 4, in add
num = int(n)
ValueError: invalid literal for int() with base 10: 'a'
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# 轉(zhuǎn)換之后拋出的異常:我們可以看到冻河,這里的異常錯(cuò)誤信息非常明確了
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Traceback (most recent call last):
File "D:/resp_work/PY_WORK/備課/模塊化開發(fā)/demo05/demo02.py", line 8, in <module>
add()
File "D:/resp_work/PY_WORK/備課/模塊化開發(fā)/demo05/demo02.py", line 6, in add
raise ValueError("這里需要一個(gè)數(shù)字箍邮,您輸入了非數(shù)字字符")
ValueError: 這里需要一個(gè)數(shù)字茉帅,您輸入了非數(shù)字字符
拋出異常有兩種情況,第一種情況锭弊,當(dāng)前代碼中可能存在異常堪澎,如果一旦出現(xiàn)異常直接拋出,讓調(diào)用者進(jìn)行后續(xù)的處理味滞,第二種情況樱蛤,當(dāng)前代碼中可能存在異常,但是出現(xiàn)異常的錯(cuò)誤提示信息非常不明確剑鞍,需要轉(zhuǎn)換成我們定義的另一種異常拋出異常昨凡,讓調(diào)用者更加明確出現(xiàn)的問題
不論是異常處理,還是拋出異常蚁署,核心都是為了更加方便的解決問題便脊!
3.3. 異常處理的方式3——拋出自定義異常
如果系統(tǒng)提供的異常不一定符合我們的需要,如用戶登錄失敗光戈,需要提示一個(gè)賬號密碼有誤的異常信息哪痰,python中是沒有提供這樣的異常對象的,需要開發(fā)人員自定義異常來進(jìn)行處理
我們從前面的內(nèi)容中已經(jīng)知道久妆,所有的異常對象都是直接或者間接繼承自BaseException
所以自定義異常如下:
# 自定義異常
class MyException(BaseException):
pass
# 函數(shù)處理
def add():
try:
n = input("請輸入一個(gè)數(shù)字:")
num = int(n)
except ValueError as e:
# 拋出自定義異常信息
raise MyError("這里需要一個(gè)數(shù)字晌杰,您輸入了非數(shù)字字符%s" % n)
add()
自定義異常,在一定程度上擴(kuò)展了異常的功能筷弦,更加方便我們在程序中進(jìn)行不同錯(cuò)誤的不同的處理手段和錯(cuò)誤提示信息肋演,使用的時(shí)候根據(jù)實(shí)際需要進(jìn)行處理即可!
4. 常見的異常
BaseException 所有異常的基類
SystemExit 解釋器請求退出
KeyboardInterrupt 用戶中斷執(zhí)行(通常是輸入^C)
Exception 常規(guī)錯(cuò)誤的基類
StopIteration 迭代器沒有更多的值
GeneratorExit 生成器(generator)發(fā)生異常來通知退出
StandardError 所有的內(nèi)建標(biāo)準(zhǔn)異常的基類
ArithmeticError 所有數(shù)值計(jì)算錯(cuò)誤的基類
FloatingPointError 浮點(diǎn)計(jì)算錯(cuò)誤
OverflowError 數(shù)值運(yùn)算超出最大限制
ZeroDivisionError 除(或取模)零 (所有數(shù)據(jù)類型)
AssertionError 斷言語句失敗
AttributeError 對象沒有這個(gè)屬性
EOFError 沒有內(nèi)建輸入,到達(dá)EOF 標(biāo)記
EnvironmentError 操作系統(tǒng)錯(cuò)誤的基類
IOError 輸入/輸出操作失敗
OSError 操作系統(tǒng)錯(cuò)誤
WindowsError 系統(tǒng)調(diào)用失敗
ImportError 導(dǎo)入模塊/對象失敗
LookupError 無效數(shù)據(jù)查詢的基類
IndexError 序列中沒有此索引(index)
KeyError 映射中沒有這個(gè)鍵
MemoryError 內(nèi)存溢出錯(cuò)誤(對于Python 解釋器不是致命的)
NameError 未聲明/初始化對象 (沒有屬性)
UnboundLocalError 訪問未初始化的本地變量
ReferenceError 弱引用(Weak reference)試圖訪問已經(jīng)垃圾回收了的對象
RuntimeError 一般的運(yùn)行時(shí)錯(cuò)誤
NotImplementedError 尚未實(shí)現(xiàn)的方法
SyntaxError Python 語法錯(cuò)誤
IndentationError 縮進(jìn)錯(cuò)誤
TabError Tab 和空格混用
SystemError 一般的解釋器系統(tǒng)錯(cuò)誤
TypeError 對類型無效的操作
ValueError 傳入無效的參數(shù)
UnicodeError Unicode 相關(guān)的錯(cuò)誤
UnicodeDecodeError Unicode 解碼時(shí)的錯(cuò)誤
UnicodeEncodeError Unicode 編碼時(shí)錯(cuò)誤
UnicodeTranslateError Unicode 轉(zhuǎn)換時(shí)錯(cuò)誤
Warning 警告的基類
DeprecationWarning 關(guān)于被棄用的特征的警告
FutureWarning 關(guān)于構(gòu)造將來語義會有改變的警告
OverflowWarning 舊的關(guān)于自動(dòng)提升為長整型(long)的警告
PendingDeprecationWarning 關(guān)于特性將會被廢棄的警告
RuntimeWarning 可疑的運(yùn)行時(shí)行為(runtime behavior)的警告
SyntaxWarning 可疑的語法的警告
UserWarning 用戶代碼生成的警告