1. 程式人生 > >Python學習筆記-異常處理

Python學習筆記-異常處理

異常處理

Python Errors and Exceptions 官方文件

  • 引發異常:
    • 語法: 使用raise 關鍵字, raise either an exception instance or an exception class (a class that derives from Exception).
      raise NameError('wrong name')
      樣例輸出:
      Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: wrong name
    • 常見的內建異常類:
      • Exception:
      • AttributeError: 引用屬性或給它賦值失敗
      • IndexError: 使用了序列中不存在的索引時引發, 為Lookup Error子類
      • KeyError: 使用對映中不存在的索引時引發, 為Lookup Error子類
      • NameError: 找不到對應名稱(變數)時引發
      • TypeError: 將內建操作或函式用於型別不正確的物件時引發
      • ValueError: 將內建操作或函式用於這樣的物件時引發, 型別正確但是包含的值不對
  • 建立異常(User-defined Exception):
    • 語法: 建立一個異常類, 必須要保證直接或間接的繼承Exception父類 Exceptions should typically be derived from the Exception class, either directly or indirectly.
    • 樣例: class MyError(Exception): pass
    • 細節:
      • 類儘量簡單, 只需要定義一些關於Error的屬性, 方便傳給handler進行異常處理
      • 如果在一個module當中需要定義多個Exception Class, 先定義一個BaseError Class, 讓其他的subclass Error來繼承該BaseError Class: When creating a module that can raise several distinct errors, a common practice is to create a base class for exceptions defined by that module, and subclass that to create specific exception classes for different error conditions:
      • 異常的定義應當以'Error'結尾,這樣符合內建異常的命名規範
  • 處理異常
    • 語法: try/except 關鍵字
    • 樣例: try 後面可以接 0個 exception, 1個 exception, 多個exception
      • 0個exception(危險的操作,儘量避免) 表示一次打包處理所有的異常
      • 1個異常: 只有異常名稱對應於try當中丟擲的異常時才會被執行
      • 多個異常:
        • 樣例: exception (NameError, TrancisionError, ValueError): pass 三個當中任意一個滿足時,都會catch並處理
        • 細節: 語法當中的( )不能省略, except RuntimeError, TypeError is not equivalent to except (RuntimeError, TypeError): but to except RuntimeError as TypeError
    • try關鍵字的執行流程:
      The try statement works as follows.
      First, the try clause (the statement(s) between the try and except keywords) is executed.
      If no exception occurs, the except clause is skipped and execution of the try statement is finished.
      If an exception occurs during the execution of the try clause, the rest of the clause is skipped. Then if its type matches the exception named after the except keyword, the except clause is executed, and then execution continues after the try statement.
      If an exception occurs which does not match the exception named in the except clause, it is passed on to outer try statements; if no handler is found, it is an unhandled exception and execution stops with a message as shown above.

    • 對except子句當中訪問異常物件本身的操作:
      • 為exception instance 命名, 以便於在異常的內部進行呼叫
      • 為異常物件新增attribute: we can add arguments to the exception. The except clause may specify a variable after the exception name (or tuple). The variable is bound to an exception instance with the arguments stored in instance.args. 必須要對instance命名, 然後才能在之後面進行呼叫
    • 異常當中的else語句: 執行沒有異常時需要做的事情
      The try … except statement has an optional else clause, which, when present, must follow all except clauses. It is useful for code that must be executed if the try clause does not raise an exception. For example:

try:
    <statements>  # Run this main action first
except <name1>:
    <exc1>           # 當 try中發生了 name1 的異常時執行exc1
except <name2>:
    <exc2>           # 當 try中發生了 name2 的異常時執行exc2
except (<name3>, <name4>:
    <exc3>           # 當 try中發生了 name3 OR name4 的異常時執行exc3
else: 
    <exc4>           # 當代碼沒有異常時執行exc4
finally: 
    <final>            #不管程式碼有沒有異常都會執行final, 通常用來做一些清理工作
  • 細節: 異常處理當中的幾點基本原則:
    • 注意異常的粒度,try當中不應當放入過多的程式碼,過多的程式碼會使得異常的定位難以定位, 儘量只在可能丟擲異常的語句塊前面放入try語句
    • 謹慎使用單獨的except語句處理所有異常, 最好能夠定位具體的異常。因為不加具體型別的except可能會掩蓋真實的錯誤
    • 樣例:
    import sys
    try: 
        print a
        b = 0
        print a / b
    except:
        sys.exit("ZeroDivisionError:Can not division zero")
    程式列印"ZeroDivisionError: Can not division zero", 但是這不是真正的錯誤原因, 真正的錯誤原因是a 在使用之前沒有進行定義
    • 注意異常的捕獲順序: 小 -> 大 因為異常的捕獲是順序執行的, 小的在前可以直接精確定位異常, 而大的在前如果鎖定不了異常的話, 還要再次執行小的,多此一舉,大小的劃分根據內建異常的繼承結構來確定。原則是如果異常能夠在被捕獲的位置處理就應當及時的進行處理, 不能處理也應當以合適的方式向上層丟擲。向上層傳遞時使用raise關鍵字即可。大類錯誤可以捕捉子類中的錯誤

【25/91建議】避免finally 可能發生的陷阱:

  • 陷阱: 異常遮蔽 OR 設計好的return語句被遮蔽
    • 當finally 語句之中出現了returnbreak語句時, 臨時儲存的異常 和 try當中原本被設計好的return語句將被丟失
    • 樣例:
    def ReturnTest(a):
        try:
            if a <= 0:
                raise ValueError("data can not be negative")
            else:
                return a
        except ValueError as e:
            print e
        finally:
            print("The End!")
            return -1
    
        print ReturnTest(0) # 輸出"The End!" "-1" 
        print ReturnTest(1) # 輸出"The End!" "-1"  出現問題

finally 當中的return在try 中的else 語句之前執行, 所以會直接執行 finally 當中的return -1 而不是try 當中的 return a

  • 異常處理模板:
    • try/finally結構: 如果既要將異常向上傳播, 又要在異常發生時進行清理工作
    • 樣例: read方法丟擲的異常會向上傳播給呼叫方, 而finally程式碼塊中的handle.close方法則一定能被執行。open方法必須放在try語句外, 這樣如果開啟檔案時發生異常會跳過finally塊
    handle = open('/tmp/random_data.txt') # May raise IOError
    try:
        data = handle.read() # May raise UnicodeDecode Error
    finally:
        handle.close() # Always runs after try
    • try/except/else結構: 清晰描述哪些異常由自己的程式碼處理、哪些異常會傳給上一級。如果try程式碼中沒有發生異常, 那麼就執行else語句, 有了這種else 語句, 我們就可以儘量縮減try中的程式碼量。
    • 樣例:
    def load_json_key(data, key):
        try:
            result_dict = json.loads(data) # 嘗試讀取一個JSON檔案
        except ValueError as e:
            raise KeyError from e  # 如果程式碼不是有效的JSON格式, 會產生ValueError
        else:
            return result_dict[key] # 程式碼是有效的JSOn格式, 進行處理
    • 全套四合一 try/except/else/finally結構
    • 樣例:
    def divide_json(path):
    handle = open(path, 'r+')
    try:
        data = handle.read()
        op = json.loads(data)
        value = (
            op['numerator'] /
            op['denominator']) # May raise ZeroDivisionError
    except ZeroDivisionError as e:
        return UNDEFINED
    else:
        op['result'] = value
        result = json.dumps(op)
        handle.seek(0)
        handle.write(result) # May raise IOError
        return value
    finally:
        handle.close() # Always runs
  • 使用內建的logging模組 來記錄exception資訊
  • 樣例:
def main():
    try:
        bar('0')
    except Exception as e:
        logging.exception(e)