本文是對Python異常處理機制的總結,較為全面的介紹了Python異常處理的常用內置類,即幾種異常捕獲/處理句式結構,主動觸發異常,斷言,with上下文管理協議,自定義異常類等內容。 ...
1 引言
在我們調試程式時,經常不可避免地出現意料之外的情況,導致程式不得不停止運行,然後提示大堆提示信息,大多是這種情況都是由異常引起的。異常的出現一方面是因為寫代碼時粗心導致的語法錯誤,這種錯誤在程式編譯時就可以發現;另一方面也可能是因為程式邏輯錯誤,這種錯誤往往是不可避免地,只能通過異常處理來防止程式退出。
2 異常類型
Python自帶的異常處理機制非常強大,提供了很多內置異常類,可向用戶準確反饋出錯信息。Python是面向對象語言,認為一切皆對象,所以異常也是對象。Python異常處理機制中的BaseException是所有內置異常的基類,但用戶定義的類並不直接繼承BaseException,所有的異常類都是從Exception繼承,且都在exceptions模塊中定義。Python自動將所有異常名稱放在內建命名空間中,所以程式不必導入exceptions模塊即可使用異常。
Python內置異常類繼承層次結構如下:
BaseException # 所有異常的基類 +-- SystemExit # 解釋器請求退出 +-- KeyboardInterrupt # 用戶中斷執行(通常是輸入^C) +-- GeneratorExit # 生成器(generator)發生異常來通知退出 +-- Exception # 常規異常的基類 +-- StopIteration # 迭代器沒有更多的值 +-- StopAsyncIteration # 必須通過非同步迭代器對象的__anext__()方法引發以停止迭代 +-- ArithmeticError # 各種算術錯誤引發的內置異常的基類 | +-- FloatingPointError # 浮點計算錯誤 | +-- OverflowError # 數值運算結果太大無法表示 | +-- ZeroDivisionError # 除(或取模)零 (所有數據類型) +-- AssertionError # 當assert語句失敗時引發 +-- AttributeError # 屬性引用或賦值失敗 +-- BufferError # 無法執行與緩衝區相關的操作時引發 +-- EOFError # 當input()函數在沒有讀取任何數據的情況下達到文件結束條件(EOF)時引發 +-- ImportError # 導入模塊/對象失敗 | +-- ModuleNotFoundError # 無法找到模塊或在在sys.modules中找到None +-- LookupError # 映射或序列上使用的鍵或索引無效時引發的異常的基類 | +-- IndexError # 序列中沒有此索引(index) | +-- KeyError # 映射中沒有這個鍵 +-- MemoryError # 記憶體溢出錯誤(對於Python 解釋器不是致命的) +-- NameError # 未聲明/初始化對象 (沒有屬性) | +-- UnboundLocalError # 訪問未初始化的本地變數 +-- OSError # 操作系統錯誤,EnvironmentError,IOError,WindowsError,socket.error,select.error和mmap.error已合併到OSError中,構造函數可能返回子類 | +-- BlockingIOError # 操作將阻塞對象(e.g. socket)設置為非阻塞操作 | +-- ChildProcessError # 在子進程上的操作失敗 | +-- ConnectionError # 與連接相關的異常的基類 | | +-- BrokenPipeError # 另一端關閉時嘗試寫入管道或試圖在已關閉寫入的套接字上寫入 | | +-- ConnectionAbortedError # 連接嘗試被對等方中止 | | +-- ConnectionRefusedError # 連接嘗試被對等方拒絕 | | +-- ConnectionResetError # 連接由對等方重置 | +-- FileExistsError # 創建已存在的文件或目錄 | +-- FileNotFoundError # 請求不存在的文件或目錄 | +-- InterruptedError # 系統調用被輸入信號中斷 | +-- IsADirectoryError # 在目錄上請求文件操作(例如 os.remove()) | +-- NotADirectoryError # 在不是目錄的事物上請求目錄操作(例如 os.listdir()) | +-- PermissionError # 嘗試在沒有足夠訪問許可權的情況下運行操作 | +-- ProcessLookupError # 給定進程不存在 | +-- TimeoutError # 系統函數在系統級別超時 +-- ReferenceError # weakref.proxy()函數創建的弱引用試圖訪問已經垃圾回收了的對象 +-- RuntimeError # 在檢測到不屬於任何其他類別的錯誤時觸發 | +-- NotImplementedError # 在用戶定義的基類中,抽象方法要求派生類重寫該方法或者正在開發的類指示仍然需要添加實際實現 | +-- RecursionError # 解釋器檢測到超出最大遞歸深度 +-- SyntaxError # Python 語法錯誤 | +-- IndentationError # 縮進錯誤 | +-- TabError # Tab和空格混用 +-- SystemError # 解釋器發現內部錯誤 +-- TypeError # 操作或函數應用於不適當類型的對象 +-- ValueError # 操作或函數接收到具有正確類型但值不合適的參數 | +-- UnicodeError # 發生與Unicode相關的編碼或解碼錯誤 | +-- UnicodeDecodeError # Unicode解碼錯誤 | +-- UnicodeEncodeError # Unicode編碼錯誤 | +-- UnicodeTranslateError # Unicode轉碼錯誤 +-- Warning # 警告的基類 +-- DeprecationWarning # 有關已棄用功能的警告的基類 +-- PendingDeprecationWarning # 有關不推薦使用功能的警告的基類 +-- RuntimeWarning # 有關可疑的運行時行為的警告的基類 +-- SyntaxWarning # 關於可疑語法警告的基類 +-- UserWarning # 用戶代碼生成警告的基類 +-- FutureWarning # 有關已棄用功能的警告的基類 +-- ImportWarning # 關於模塊導入時可能出錯的警告的基類 +-- UnicodeWarning # 與Unicode相關的警告的基類 +-- BytesWarning # 與bytes和bytearray相關的警告的基類 +-- ResourceWarning # 與資源使用相關的警告的基類。被預設警告過濾器忽略。
3 異常捕獲與處理
當發生異常時,我們就需要對異常進行捕獲,然後進行相應的處理。使用Python異常處理機制時,把可能發生錯誤的語句放在try模塊里,用except來處理異常,每一個try,都必須至少對應一個except。Python異常處理機制常用的幾種異常捕獲和處理結構如下:
第一種:try - except
try: <語句> except: <異常處理>
這種結構使用簡單,但可能會引發一些設計問題:儘管使用方便,但可能捕獲與程式無關、意料之外的系統異常,而且可能意外攔截其他處理器的異常。例如,在Python中,即表示系統離開調用(sys.exit())也會出發異常,然而這種異常我們通常不需要捕獲。所以,這種結構儘量少用。
import time import sys try: while True: a = int(input('請輸入一個數字:')) if a==0: sys.exit() else: print('您輸入的數字是:{}'.format(a)) time.sleep(1) except: print('發生異常了……')
當輸入數字0時,輸出如下:
請輸入一個數字:0
發生異常了……
事實上,系統知識正常退出,並不算異常,但是只使用except,Python會將系統離開調用當做異常來捕獲。
(2)try-except<異常名>
try: <語句> except <異常名> [as e]: <異常處理>
except中,as e是可選的,意思是將捕獲的異常類實例化對象賦值給e(當然也可以用其他變數名),在except下麵的代碼塊中,我們將可以通過這個e訪問異常實例化對象中的方法和數據。另外,except子句的個數理論上是不限的,不過不能將父類置於子類前面。在上文中提到,Exception類是所有Python異常類的父類,所以except Exception將可以捕獲任何異常,換句話說,它是萬能異常處理句式。
try: a = int(input('請輸入一個數字:')) except ValueError as e: print(e)
當輸入一個非數字類字元時,輸出如下:
請輸入a的值:j
invalid literal for int() with base 10: 'j'
如果輸入b的值為0,輸出如下:
請輸入a的值:1
請輸入b的值:0
division by zero
(3)try-except (<異常類1>, <異常類2>, ...)
try: <語句> except (<異常類1>, <異常類2>, ...): <異常處理>
try: a = int(input('請輸入a的值:')) b = int(input('請輸入b的值:')) c = a/b except (ValueError , ZeroDivisionError) as e: print(e)
當輸入一個非數字類字元時,輸出如下:
請輸入a的值:j
invalid literal for int() with base 10: 'j'
如果輸入b的值為0,輸出如下:
請輸入a的值:1
請輸入b的值:0
division by zero
(4)try-except-else
try: <語句> except <異常名>: <異常處理> else: <語句> # try語句中沒有異常則執行此段代碼
如果說except是在try中代碼拋出異常時執行,那麼else語句下麵的代碼將在try順利執行(沒有拋出任何異常)的情況下才會執行。
try: a = int(input('請輸入a的值:')) b = int(input('請輸入b的值:')) c = a/b except (ValueError , ZeroDivisionError) as e: print(e) else: print('a/b的結果為:{}'.format(c))
當輸入a和b的值都為數字時,才會執行else部分代碼,輸出結果如下:
請輸入a的值:4
請輸入b的值:2
a/b的結果為:2.0
(5)try-except-finally
try: <語句> except <異常類>: <異常處理> finally: <語句> # 不管try中代碼是否拋出異常,都會執行此段代碼
finally中的代碼無論try中代碼是否拋出異常都會執行。
try: a = int(input('請輸入a的值:')) b = int(input('請輸入b的值:')) c = a/b except (ValueError , ZeroDivisionError) as e: print(e) else: print('a/b的結果為:{}'.format(c)) finally: print('無論你輸入什麼值,finally都會執行……')
4 主動拋出異常(raise)
有時候,異常可以作為代碼運行的標誌,通過主動觸發異常可以改變代碼的運行路線,從而提高代碼健壯性。主動觸發異常需使用raise關鍵字,其語法結構如下:
raise [Exception [, args [, traceback]]]
def fun(x , y): try: print('fun()方法開始執行……') if isinstance(x,int) and isinstance(y,int): return x+y else: raise TypeError('類型錯誤') except Exception as e: print(e) finally: print('fun()方法執行結束……') fun(2 , '3')
輸出結果:
fun()方法開始執行……
類型錯誤
fun()方法執行結束……
5 斷言(assert)
assert語句根據後面的表達式的真假來控製程序流。 asset語法結構如下:
assert expression,'information'
若為expression結果為True,則往下執行。若為False,則中斷程式並調用預設的異常處理器拋出AssertionError異常,同時輸出指定的提示信息。
def fun(x): print('fun()方法開始執行……') assert x<0 , '拋出異常,x小於0' print('fun()方法執行結束……') try: fun(2) except Exception as e: print(e)
輸出結果:
fun()方法開始執行……
拋出異常,x小於0
可以發現,列印輸出第一行語句之後,由於斷言失敗,拋出異常,程式直接退出。
6 with/as上下文管理器
with/as語句通常是作為try/finally語句的替代方案,不過with/as更加優雅。在有一些任務中,可能事先需要設置,然後不管在任務過程中是否順利(有無異常拋出),最後後做清理工作。對於這種場景, with/as語句提供了一種非常方便的處理方式。一個很好的例子是文件處理,你需要獲取一個文件句柄,從文件中讀寫數據,但不管讀寫數據是否有異常發生,最後都要關閉文件句柄。
with/as語句的基本格式如下:
with expression [as variable] :
with-block
在這裡的expression會返回一個對象,as子句是可選的,當存在as子句時,expression返回的對象會賦值給variable。
使用with/as語句將一段字元串寫入文件:
with open('data.txt' , 'w') as myfile: myfile.write('123456789') 不適用with/as語句,如果要實現同樣的效果,只能這麼寫: try: myfile = open('data.txt' , 'w') myfile.write('123456789') except Exception as e: print(e) finally: myfile.close()
7 自定義異常類
如果Python提供的內置異常內不滿足使用要求,那麼,可以自定義一個異常類。自定義異常類必須繼承Exception類,並且在使用時,必須通過raise關鍵字自動觸發。
class MyError(Exception): def __init__(self, info): self.info = info def __str__(self): return '{}:{}'.format(self.__class__ .__name__, self.info) try: raise MyError('自定義的異常……') except MyError as e : print(e)
輸出結果:
MyError:自定義的異常……
8 總結
本文是對Python異常處理機制的總結,較為全面的介紹了Python異常處理的常用內置類,即幾種異常捕獲/處理句式結構,主動觸發異常,斷言,with上下文管理協議,自定義異常類等內容。