Python異常處理回顧與總結

奧辰發表於2018-12-21

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上下文管理協議,自定義異常類等內容。

相關文章