Python異常資訊的捕獲和處理
什麼是異常
異常是一種影響程式執行的事件。當發生超出程式規則之外的事情時,程式就會“一臉懵逼”而卡在那裡,嚴重的程式甚至直接選擇奔潰。
異常的丟擲機制:
- 如果在執行時發生異常,直譯器會查詢相應的處理語句(稱為handler).
- 要是在當前函式裡沒有找到的話,它會將異常傳遞給上層的呼叫函式,看看那裡能不能處理。
- 如果在最外層(全域性“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')
其他說明
- 在2.x時代,所有型別的物件都是可以被直接丟擲的,在3.x時代,只有繼承自BaseException的物件才可以被丟擲。
- 2.x raise語句使用逗號將丟擲物件型別和引數分開,3.x取消了這種奇葩的寫法,直接呼叫建構函式丟擲物件即可。
- 在2.x時代,異常在程式碼中除了表示程式錯誤,還經常做一些普通控制結構應該做的事情,在3.x中可以看出,設計者讓異常變的更加專一,只有在錯誤發生的情況才能去用異常捕獲語句來處理。
- 捕獲一個通用異常時,在Exception和BaseException之間,建議使用Exception。雖然BaseException包含了Exception,但是Exception所包含的範圍就足夠了。