使用者:Xyy23330121/Python/處理異常

來自維基學院


我們之前已經接觸到一些報錯了。比如在可變序列操作表內,就列出了許多會因為「輸入的數據與函數需求不符」導致的報錯。

這些報錯除了在試運行程序時,可以簡潔直白地說明錯誤位置和錯誤原因。通過以下的內容,還可以用作代碼的一部分。

try 語句[編輯 | 編輯原始碼]

try 語句會先執行 try 子句,如果沒有觸發異常,則跳過 except 子句。如果觸發異常,則會跳過 try 子句剩下的部分,執行 except 子句。

我們看以下示例:

while True:
    try:
        x = float(input("请输入一个数字: "))
        break
    except:
        print("请重新输入。")

該示例會要求用戶一直輸入內容,直到輸入有效的數字為止。

匹配異常[編輯 | 編輯原始碼]

異常類型
一切引發的異常,都是異常類
或異常類的實例。

我們可以在 except 後面添加異常的類型,來匹配異常。

對於一個 except 語句。當引發的異常不是 except 右側給出的異常類型(或其子類)的實例時,跳過該 except 語句。

當引發的異常是右側異常類型的子類,或異常類型元組中某個類型的子類時,會執行該 except 子句,並跳過之後的所有的 except 語句。

我們看以下示例:

while True:
    try:
        x = float(input("请输入一个数字: "))
        break
    except ValueError:
        #当 try 子句中引发的异常是ValueError或其子类的实例时,执行以下语句。
        print("抱歉,输入的并不是有效的数字,请重新输入。")
    except (AttributeError, TypeError):
        #当 try 子句中引发的异常是AttributeError或TypeError或两者子类的实例时,执行以下语句。
        pass
    except Exception:
        #只要引发可处理的异常,就执行以下语句。
        pass

和以下示例:

while True:
    try:
        x = float(input("请输入一个数字: "))
        break
    except ValueError:
        #当 try 子句中引发的异常是ValueError或其子类的实例时,执行以下语句。
        print("抱歉,输入的并不是有效的数字,请重新输入。")
    except Exception:
        #只要引发异常,执行以下语句。
        pass
    except (AttributeError, TypeError):
        #以下语句不会被执行,因为以上AttributeError和TypeError都是Exception的子类。
        #总是会先匹配到 Exception 并跳过对 AttributeError 和 TypeError 的匹配。
        pass

以上的 except Exception:except: 是完全等同的。

獲取異常類型[編輯 | 編輯原始碼]

try:
    raise Exception
except Exception as e:
    print(type(e))  #输出:<class 'Exception'>

利用 as 關鍵詞,我們可以將所引發的異常賦值給其後的變量名,並在處理異常時訪問關於異常的信息。

raise 主動引發異常[編輯 | 編輯原始碼]

我們可以用 raise 語句主動引發異常。比如以下示例:

raise Exception
print("由于已经引发异常,本内容不会被输出。")

其輸出為:

Traceback (most recent call last):
  ...
    raise Exception
Exception

raise 語句中,raise 後面跟着的是一個異常類型,或異常類型的實例。

對於大多數異常類型,我們可以創建帶有注釋的實例。比如:

a = Exception('注释')
raise a

這在許多情況下很有用。

except: raise[編輯 | 編輯原始碼]

特別的,如果在 except 子句中,可以這樣使用 raise 語句:

try:
    1/0
except:
    print('引发了错误')
    raise

其輸出為:

引发了错误
Traceback (most recent call last):
  ...
    1/0
    ~^~
ZeroDivisionError: division by zero

此時,raise 語句會原封不動地將正在處理的錯誤重新引發一遍。

raise from[編輯 | 編輯原始碼]

為了表示一個異常是另一個異常的直接後果,可以用這種方法:

raise CausedException from Exception

比如以下示例:

try: 1 / 0
except ZeroDivisionError as e:
    raise ValueError("除数不能为零") from e

其輸出為:

Traceback (most recent call last):
  ...
    try: 1 / 0
         ~~^~~
ZeroDivisionError: division by zero

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  ...
    raise ValueError("除数不能为零") from e
ValueError: 除数不能为零

異常鏈[編輯 | 編輯原始碼]

如果在處理異常時,又引發一個異常,則會將正在處理的異常加入到新異常的注釋中,比如:

try: 1 / 0
except ZeroDivisionError:
    raise ValueError

其輸出為:

Traceback (most recent call last):
  ...
    try: 1 / 0
         ~~^~~
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  ...
    raise ValueError
ValueError

raise from None[編輯 | 編輯原始碼]

作為 raise 語句的特殊用法。如果使用 raise Exception from None,則會禁用異常鏈。

try: 1 / 0
except ZeroDivisionError:
    raise ValueError from None

其輸出為:

Traceback (most recent call last):
  ...
    raise ValueError from None
ValueError

異常類型[編輯 | 編輯原始碼]

一切引發的異常,都是異常類或異常類的實例。而一切異常類型都是 BaseException 的子類。

由於 BaseException 的一些子類包含「系統退出」等異常——此類異常一旦引起,Python 必須退出,因此不能對該類異常進行處理,也不應在代碼中匹配 BaseException 類。

可處理的異常的基類是 Exception 。所有內置的非系統退出類的異常都是該類的子類。所有用戶自定義異常也應當是該類的子類。

異常的屬性與方法[編輯 | 編輯原始碼]

  1. args
    包含錯誤信息的元組。對於一般的異常,該元組只包含一個給出錯誤信息的字符串。對於一些特殊的內置異常,比如 OSError,該元組中的元素可以有特殊的含義。
  2. with_traceback(traceback)
    該方法為異常提供了內置異常中,提示錯誤位置的部分。
  3. __traceback__
    with_traceback方法中,用於存儲該方法提示的屬性。
  4. add_note(note)
    該方法向該異常的注釋列表添加注釋。該方法一般由 Python 在創建「異常鏈」時自動調用,讀者不必掌握該方法的內容。
  5. __notes___
    異常的注釋列表。

創建異常對象[編輯 | 編輯原始碼]

我們可以通過e = Exception(*args)方法創建異常。其中傳入args的內容會原封不動放置到 e.args 裡面。

traceback[編輯 | 編輯原始碼]

我們一般不手動創建 traceback 對象,而是在引發錯誤時提取錯誤的 traceback 對象並進行使用。比如:

import sys
try:
    1/0
except:
    print('引发了错误')
    tb = sys.exception().__traceback__
    raise Exception().with_traceback(tb) from None

自定義異常類型[編輯 | 編輯原始碼]

我們之前已經可以自定義異常的屬性,現在我們可以自定義異常的類型。

自定義異常類型的方法和一般的類型是一致的,不過要求它必須繼承自Exception,比如:

class MyException(Exception): pass
class MyException1(ZeroDivisionError): pass

異常的提示功能[編輯 | 編輯原始碼]

對於大多數情況,引發任何內置異常的或自定義的異常,都足以滿足程序邏輯的需求。但隨意引發的異常對於修復異常是沒有幫助的。

讀者在引發異常或自定義異常時,應注意提供詳實的異常信息,並使用正確的內置異常、或自定義一個明確的異常名稱。

finally 與 else[編輯 | 編輯原始碼]

try 語句的結構中,還可以添加 finally 語句和 else 語句。其順序可以如下表示:

try: pass
except: pass
else: pass
finally: pass

else[編輯 | 編輯原始碼]

try 子句沒有引發任何異常時,如果有 else 語句,則會執行其子句。

finally[編輯 | 編輯原始碼]

無論之前異常的處理情況如何,finally 子句都會被執行。具體來講:

  1. 如果所有異常都被匹配並處理,則正常執行 finally 子句。
  2. 如果有異常未被處理,則執行 finally 子句,然後再引發異常。

比如:

try:
    1/0
except IndexError:
    pass
finally:
    print("先执行finally,再引发异常")
先执行finally再引发异常
Traceback (most recent call last):
  ...
    1/0
    ~^~
ZeroDivisionError: division by zero

異常組與 except*[編輯 | 編輯原始碼]

except* 語句用於處理異常組。

異常組[編輯 | 編輯原始碼]

異常組是一種特殊的異常。其創建方法為:

ExceptionGroup(msg, excs)

其中,msg 是一個包含錯誤信息的字符串,而 excs 是包含多個異常的可迭代序列。excs 中的異常只能為 Exception 的子類。

類似 ExceptionBaseException,也有 BaseExceptionGroup。與 ExceptionGroup 不同的是,BaseExceptionGroup 中可以包含 BaseException 的子類。

匹配方法[編輯 | 編輯原始碼]

try:
    raise ExceptionGroup("异常组",
     [ValueError("错误:ValueError"),
      TypeError("错误:TypeError1"),
      TypeError("错误:TypeError2")])
except* TypeError as e:
    print(f'捕捉到{e.exceptions}')
except* ValueError as e:
    print(f'捕捉到{e.exceptions}')

輸出為:

捕捉到(TypeError('错误:TypeError1'), TypeError('错误:TypeError2'))
捕捉到(ValueError('错误:ValueError'),)

except* 語句會有選擇地匹配異常組中、符合的所有異常,並由下一個 except* 語句來嘗試匹配剩下的部分。

異常組的屬性與方法[編輯 | 編輯原始碼]

message[編輯 | 編輯原始碼]

對應創建異常組時,使用的參數 msg

excs[編輯 | 編輯原始碼]

對應創建異常組時,使用的參數 excs。它包含參數中所有異常組成的元組。

該屬性為只讀屬性。

subgroup(condition)[編輯 | 編輯原始碼]

返回一個當前異常組中、匹配異常類型 condition 的異常組成的子組。如果沒有匹配的異常,則返回 None

子組將和原異常組有相同的 exception 信息。

split(condition)[編輯 | 編輯原始碼]

返回一個有兩個元素的元組。元組第一個元素即為 self.subgroup(condition) ,而第二個元素為剩餘的異常組成的子組。

子組將和原異常組有相同的 exception 信息。

derive(excs)[編輯 | 編輯原始碼]

subgroup 方法和 split 方法調用的、創建子異常組的方法。

如果要自定義異常組的子類,則應當重寫該方法。比如:

class MyGroup(ExceptionGroup):
    def derive(self, excs):
        return MyGroup(self,message, excs)

自定義子類的提示[編輯 | 編輯原始碼]

要注意,BaseExceptionGroup 定義了 __new__ 方法。因此,在創建子類實例時,如果要更改構造子類實例的參數名稱或順序,或給子類添加屬性,應當重寫 __new__ 方法,而非重寫 __init__ 方法。比如:

class MyGroup(ExceptionGroup):
    def __new__(cls, errors, exit_code):
        self = super().__new__(Errors, str(exit_code), errors)
        self.exit_code = exit_code
        return self
    def derive(self, excs):
        return MyGroup(excs, self.exit_code)