1. 程式人生 > >Python異常資訊的捕獲和處理

Python異常資訊的捕獲和處理

什麼是異常

異常是一種影響程式執行的事件。當發生超出程式規則之外的事情時,程式就會“一臉懵逼”而卡在那裡,嚴重的程式甚至直接選擇奔潰。

異常的丟擲機制:

  1. 如果在執行時發生異常,直譯器會查詢相應的處理語句(稱為handler).
  2. 要是在當前函式裡沒有找到的話,它會將異常傳遞給上層的呼叫函式,看看那裡能不能處理。
  3. 如果在最外層(全域性“main”)還是沒有找到的話,直譯器就會退出,同時打印出traceback以便讓使用者找到錯誤產生的原因。

異常的型別

異常名稱 描述
BaseException 所有異常的基類
SystemExit 直譯器請求退出
KeyboardInterrupt 使用者中斷執行(通常是輸入^C)
Exception 常規錯誤的基類
StopIteration 迭代器沒有更多的值
GeneratorExit 生成器(generator)發生異常來通知退出
StandardError 所有的內建標準異常的基類
ArithmeticError 所有數值計算錯誤的基類
FloatingPointError 浮點計算錯誤
OverflowError 數值運算超出最大限制
ZeroDivisionError 除(或取模)零 (所有資料型別)
AssertionError 斷言語句失敗
AttributeError 物件沒有這個屬性
EOFError 沒有內建輸入,到達EOF 標記
EnvironmentError 作業系統錯誤的基類
IOError 輸入/輸出操作失敗
OSError 作業系統錯誤
WindowsError 系統呼叫失敗
ImportError 匯入模組/物件失敗
LookupError 無效資料查詢的基類
IndexError 序列中沒有此索引(index)
KeyError 對映中沒有這個鍵
MemoryError 記憶體溢位錯誤(對於Python 直譯器不是致命的)
NameError 未宣告/初始化物件 (沒有屬性)
UnboundLocalError 訪問未初始化的本地變數
ReferenceError 弱引用(Weak reference)試圖訪問已經垃圾回收了的物件
RuntimeError 一般的執行時錯誤
NotImplementedError 尚未實現的方法
SyntaxError Python 語法錯誤
IndentationError 縮排錯誤
TabError Tab 和空格混用
SystemError 一般的直譯器系統錯誤
TypeError 對型別無效的操作
ValueError 傳入無效的引數
UnicodeError Unicode 相關的錯誤
UnicodeDecodeError Unicode 解碼時的錯誤
UnicodeEncodeError Unicode 編碼時錯誤
UnicodeTranslateError Unicode 轉換時錯誤
Warning 警告的基類
DeprecationWarning 關於被棄用的特徵的警告
FutureWarning 關於構造將來語義會有改變的警告
OverflowWarning 舊的關於自動提升為長整型(long)的警告
PendingDeprecationWarning 關於特性將會被廢棄的警告
RuntimeWarning 可疑的執行時行為(runtime behavior)的警告
SyntaxWarning 可疑的語法的警告
UserWarning 使用者程式碼生成的警告

異常的捕獲和處理

異常就像跑得飛起的兔子,在兔子屁股後面狂追一通只會勞心勞力。正確的方法是使用陷阱去捕獲“兔子”,而常用的捕獲異常的陷阱分兩類:

try 捕獲有Python或程式本身引發的異常

raise 手工引發一個異常

try...except...else

try:
    statement1
except <name>:  # 異常名字name
    statement2
except <name>,<value>:  # 獲取異常的附加資料
    statement3
else:
    statement  # 如果無異常則執行該部分語句
這個過程的原理很簡單,打個比方:

try就好比你往外撒了一個漁網,如果有“魚”(異常),就進入到except,這部分就好比根據魚的種類做不同的動作,如果沒有魚,就執行else部分。

下面是往一個沒有寫入許可權的檔案寫資料的例子,此時會發生檔案流錯誤IOError。

try:
    file = open('testfile', 'w')
    file.write('一個測試異常的檔案')
except IOError:
    print('Error: 未找到檔案或檔案不存在')
else:
    print('寫入操作成功')
    file.close()
執行以上程式碼,結果如下:
Error: 未找到檔案或檔案不存在

使用except不帶異常型別

如果你不知道程式執行過程肯能會發生什麼異常的名字,可以直接以except捕獲所有可能的異常。當然這個方式就無法識別出具體的異常資訊。

try:
    statement1
except:
    statement2
else:
    statement3

try...finally

try-finally語句是無論是否發生異常,都將執行finally部分的程式碼,通常用於檔案的關閉等動作。可以配合except、else使用。

try:
    statement1
except:
    statement2
else:
    statement3
finally:
    statement4

繼續以上述檔案寫入的操作為例

try:
    file = open("testfile", "w")
    try:
        file.write("這是一個測試異常的檔案")
    finally:
        print("關閉檔案")
        file.close()
except IOError:
    print("Error: 未找到檔案或讀取檔案失敗")

當在try塊中丟擲一個異常,立即執行finally塊程式碼。

finally塊中的所有語句執行後,異常被再次觸發,並執行except塊程式碼。


異常的引數

上面講到try...except...else時,except除了異常型別name,還有異常引數value。通常包含錯誤字串,錯誤資料,錯誤位置等資訊。

try:
    statement1
except ExceptionType as e:
    return e.argument

觸發異常

使用raise語句觸發異常

raise [Exception [, args [, traceback]]]
語句中Exception是異常的型別(例如,NameError)引數是一個異常引數值。該引數是可選的,如果不提供,異常的引數是"None"。

最後一個引數是可選的(在實踐中很少使用),如果存在,是跟蹤異常物件。

def mtest(str):
	if str == 'Hello':
		raise NameError('無效輸入')
	if str.isdigit():
		raise TypeError('資料型別錯誤')
try:
	#mtest('1')
	mtest('Hello')
except NameError as e:
	print(e)
except TypeError as e:
	print(e)
else:
	print('Done')

為了捕獲多個異常,除了上面所示的宣告多個except語句外,還可以在一個except語句後將多個異常列成一個元組,例如:

except (zeroDivisionError, TypeError)

自定義異常型別

下面是從Exception類派生的一個自定義異常類,重寫了預設的__init__()異常。

class MyError(Exception):
	def __init__(self, value):
		self.value = value
	def __str__(self):
		return repr(self.value)

try:
	raise MyError(2*2)
except MyError as e:
	print('Exception occured, value:', e.value)

斷言assert

assert False,'error...'
print 'continue'
這個語句,先判斷assert後面緊跟的語句是True還是False。如果是True則執行print,如果是False則中斷程式,呼叫預設的異常處理器,同時輸出assert語句逗號後面的提示資訊。上述例子程式會中斷,提示error,後面的print不執行。

with...as

我們平時在使用類似檔案的流物件時,使用完畢後要呼叫close方法關閉,很麻煩。

這裡with…as語句提供了一個非常方便的替代方法:open開啟檔案後將返回的檔案流物件賦值給f,然後在with語句塊中使用。with語句塊完畢之後,會隱藏地自動關閉檔案。

如果with語句或語句塊中發生異常,會呼叫預設的異常處理器處理,但檔案還是會正常關閉。

with open('test.txt', 'r') as f:
    f.read()
    print(2/0)
print('continue')

其他說明

  1. 在2.x時代,所有型別的物件都是可以被直接丟擲的,在3.x時代,只有繼承自BaseException的物件才可以被丟擲。
  2. 2.x raise語句使用逗號將丟擲物件型別和引數分開,3.x取消了這種奇葩的寫法,直接呼叫建構函式丟擲物件即可。
  3. 在2.x時代,異常在程式碼中除了表示程式錯誤,還經常做一些普通控制結構應該做的事情,在3.x中可以看出,設計者讓異常變的更加專一,只有在錯誤發生的情況才能去用異常捕獲語句來處理。
  4. 捕獲一個通用異常時,在ExceptionBaseException之間,建議使用Exception。雖然BaseException包含了Exception,但是Exception所包含的範圍就足夠了。