Python3.5筆記
第9章 異常
什么是異常
一般情況下,在Python無法正常處理程序時就會發(fā)生異常。異常是Python的對象,表示一個錯誤。當(dāng)Python腳本發(fā)生異常時,我們需要捕獲并處理異常,否則程序會終止執(zhí)行。如:
print(a)
輸出:
Traceback (most recent call last):
File "D:/pyspace/hellopython/Chapter9.py", line 1, in <module>
print(a)
NameError: name 'a' is not defined
異常處理
處理異常,最簡單的是使用try語句處理。try語句的基本格式為:try/except。try/except語句用來檢測try語句塊中的錯誤,從而讓except語句捕獲異常信息并處理。如果你不想在發(fā)生異常時結(jié)束程序,只需要在try語句塊中捕獲異常即可。語法格式如下:
try:
<語句> #運行別的代碼
except<異常的名字>:
<語句> #如果在try部分發(fā)生了異常
示例如下:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
def test_exception(x,y):
try:
a = x/y
print('a=',a)
return a
except Exception:
print('程序發(fā)生異常,被除數(shù)為0')
test_exception(10,0)
輸出:
程序發(fā)生異常,被除數(shù)為0
拋出異常
Python使用raise語句拋出一個指定的異常。使用類時程序會自動創(chuàng)建實例。
#raise Exception
#raise NameError('this is nameerror')
def test_exception2():
try:
raise NameError('This is a NameError')
except NameError:
print('發(fā)生異常!')
#raise 如果只想知道是否拋出了異常,不想處理。加上raise語句把異常再次拋出
test_exception2()
輸出(捕獲異常后不添加raise):
發(fā)生異常!
輸出(捕獲異常后添加raise):
Traceback (most recent call last):
發(fā)生異常!
File "D:/pyspace/hellopython/Chapter9.py", line 26, in <module>
test_exception2()
File "D:/pyspace/hellopython/Chapter9.py", line 21, in test_exception2
raise NameError('This is a NameError')
NameError: This is a NameError
在Python中,內(nèi)建的異常類很多,比如:NameError、SyntaxError、TypeError、ValueError等,可以使用dir函數(shù)列出異常類的內(nèi)容,并用在raise語句中。內(nèi)建的異常類如表格:
| 異常名稱 | 描述 |
|---|---|
| Exception | 常規(guī)錯誤的基類 |
| AttributeError | 對象沒有這個屬性 |
| IOError | 輸入/輸出操作失敗 |
| IndexError | 序列中沒有此索引(index) |
| KeyError | 映射中沒有這個鍵 |
| NameError | 未聲明/初始化對象 |
| SyntaxError | 語法錯誤 |
| SystemError | 一般解釋器系統(tǒng)錯誤 |
| ValueError | 傳入無效的參數(shù) |
捕獲多個異常
Python支持在一個try/excpet語句中處理多個異常,語法如下:
try:
<語句>
except <異常名1>:
<語句>
except <異常名2>:
<語句>
示例如下:
def test_expcetion3(a,b):
try:
c = a / b
#c = name
return c
except ZeroDivisionError:
print('除數(shù)不能為0')
except NameError:
print('對象未聲明')
test_exception(10,0)
輸出:
程序發(fā)生異常,被除數(shù)為0
使用一個塊捕捉多個異常
如果需要使用一個塊捕捉多個類型異常,可以將它們作為元組列出。使用該方式時,遇到的異常類型是元組中的任意一個,都會走異常流程。實例如下:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
def test_expceiton4(a,b):
try:
c = a/b
return c
except (ZeroDivisionError,NameError,TypeError):
print('發(fā)生了異常!')
test_expceiton4(10,0)
輸出:
發(fā)生了異常!
如果我們希望多個except字句輸出同樣的信息,就沒必要在幾個except字句中重復(fù)輸入字句,放到一個異常塊中即可。
捕捉對象
如果需要輸出錯誤時的棧堆信息,可以使用as e的形式,我們稱之為捕捉對象。實例如下:
def test_expceiton5(a,b):
try:
c = a/b
return c
except (ZeroDivisionError,NameError,TypeError) as e:
print(e)
test_expceiton5(10,0)
輸出:
division by zero
全捕捉
對于逃過了捕獲指定異常的情況,我們根本無法預(yù)測會發(fā)生什么,也無法提前做任何準(zhǔn)備。此時,與其使用不是捕獲異常try/catch語句隱藏異常,不如讓程序立刻崩潰。實例如下:
def test_expceiton5(a,b):
try:
c = a/b
return c
except:
print('全捕獲異常')
test_expceiton5(10,'')
輸出:
全捕獲異常
異常中的else
try...except...else...語句,如果在try語句執(zhí)行時沒有發(fā)生異常,就會執(zhí)行else語句后的語句(如果有else)。使用else語句,比把所有語句都放在try字句里更好,這樣可以避免一些意想不到而except有沒捕獲的異常。格式如下:
try:
<語句>
except<異常名1>:
<語句>
except<異常名n>:
<語句>
else:
<語句> #如果沒有發(fā)生異常
示例如下:
def test_expceiton6(a,b):
try:
c = a/b
except:
print('全捕獲異常')
else:
print('沒有發(fā)生異常')
test_expceiton6(10,5)
輸出:
沒有發(fā)生異常
自定義異常
盡管內(nèi)建異常包含了大部分異常,可以滿足很多要求,但有時還是要創(chuàng)建自己的異常類??梢酝ㄟ^創(chuàng)建一個新的exception類擁有自己的異常,異常應(yīng)該繼承自Exception類,可以直接繼承,也可以間接繼承。
因為錯誤就是類,因此捕獲異常就是捕獲該類的一個實例,我們自己編寫的函數(shù)也可以拋出錯誤。如果要拋出錯誤,那么可以定義一個錯誤類,根據(jù)需要選擇好繼承關(guān)系,然后用raise語句拋出一個錯誤的實例。示例如下:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
# 自定義異常類最好以Error結(jié)尾,方便識別
class MyError(Exception):
def __init__(self):
pass
def __str__(self):
return 'this is my error'
def my_error_test():
try:
raise MyError()
except MyError as e:
print('exception e is:',e)
my_error_test()
輸出:
exception e is: this is my error
finally子句
Python中finally子句需要和try語句一起使用,組成try/finally的語句形式,try/finally語句無論是否發(fā)生異常與否都將執(zhí)行最后的代碼。try、except、else、finally語句可以組合使用,執(zhí)行順序為:else在except之后,finally在except和else之后。示例如下:
def use_finally(a,b):
try:
c = a/b
print('c=',c)
except Exception:
print('發(fā)生異常')
else:
print('順利上岸,沒有發(fā)生異常')
finally:
print('不管是否發(fā)生異常,我都會出現(xiàn)!')
use_finally(10,0)#測試發(fā)生異常
#use_finally(10,5)#測試不發(fā)生異常
發(fā)生異常時,輸出:
發(fā)生異常
不管是否發(fā)生異常,我都會出現(xiàn)!
不發(fā)生異常時,輸出:
c= 2.0
順利上岸,沒有發(fā)生異常
不管是否發(fā)生異常,我都會出現(xiàn)!
異常和函數(shù)
如果異常在函數(shù)內(nèi)引發(fā)而不被處理,就會傳播至函數(shù)調(diào)用的地方。如果異常在函數(shù)調(diào)用的地方也沒有被處理,就會繼續(xù)傳播,一直到達主程序。如果在主程序也沒有被處理,異常就會被Python解釋器捕獲,輸出一個錯誤信息,然后退出主程序。示例如下:
def divide_func(a,b):
return a/b
def exec_divide_func(a,b):
return divide_func(a,b)*10
def main(a,b):
return exec_divide_func(a,b)
main(10,0)
輸出:
Traceback (most recent call last):
File "D:/pyspace/hellopython/Chapter9.py", line 131, in <module>
main(10,0)
File "D:/pyspace/hellopython/Chapter9.py", line 129, in main
return exec_divide_func(a,b)
File "D:/pyspace/hellopython/Chapter9.py", line 126, in exec_divide_func
return divide_func(a,b)*10
File "D:/pyspace/hellopython/Chapter9.py", line 123, in divide_func
return a/b
ZeroDivisionError: division by zero
由執(zhí)行結(jié)果來看,divide_func函數(shù)中產(chǎn)生的異常通過divide_func和exec_divide_func函數(shù)傳播,exec_divide_func函數(shù)中的異常,通過exec_divide_func和main函數(shù)傳播,傳播到函數(shù)調(diào)用處后由Python解釋器處理,最終拋出棧堆異常。