使用者:Xyy23330121/Python/處理異常
我們之前已經接觸到一些報錯了。比如在可變序列操作表內,就列出了許多會因為「輸入的數據與函數需求不符」導致的報錯。
這些報錯除了在試運行程序時,可以簡潔直白地說明錯誤位置和錯誤原因。通過以下的內容,還可以用作代碼的一部分。
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 Exception
print("由于已经引发异常,本内容不会被输出。")
其輸出為:
Traceback (most recent call last):
...
raise Exception
Exception
raise 語句中,raise
後面跟着的是一個異常類型,或異常類型的實例。
對於大多數異常類型,我們可以創建帶有注釋的實例。比如:
a = Exception('注释')
raise a
這在許多情況下很有用。
特別的,如果在 except
子句中,可以這樣使用 raise
語句:
try:
1/0
except:
print('引发了错误')
raise
其輸出為:
引发了错误
Traceback (most recent call last):
...
1/0
~^~
ZeroDivisionError: division by zero
此時,raise
語句會原封不動地將正在處理的錯誤重新引發一遍。
為了表示一個異常是另一個異常的直接後果,可以用這種方法:
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
語句的特殊用法。如果使用 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
。所有內置的非系統退出類的異常都是該類的子類。所有用戶自定義異常也應當是該類的子類。
- args
包含錯誤信息的元組。對於一般的異常,該元組只包含一個給出錯誤信息的字符串。對於一些特殊的內置異常,比如OSError
,該元組中的元素可以有特殊的含義。 - with_traceback(traceback)
該方法為異常提供了內置異常中,提示錯誤位置的部分。 - __traceback__
with_traceback
方法中,用於存儲該方法提示的屬性。 - add_note(note)
該方法向該異常的注釋列表添加注釋。該方法一般由 Python 在創建「異常鏈」時自動調用,讀者不必掌握該方法的內容。 - __notes___
異常的注釋列表。
我們可以通過e = Exception(*args)
方法創建異常。其中傳入args
的內容會原封不動放置到 e.args
裡面。
我們一般不手動創建 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
對於大多數情況,引發任何內置異常的或自定義的異常,都足以滿足程序邏輯的需求。但隨意引發的異常對於修復異常是沒有幫助的。
讀者在引發異常或自定義異常時,應注意提供詳實的異常信息,並使用正確的內置異常、或自定義一個明確的異常名稱。
try
語句的結構中,還可以添加 finally
語句和 else
語句。其順序可以如下表示:
try: pass
except: pass
else: pass
finally: pass
當 try
子句沒有引發任何異常時,如果有 else
語句,則會執行其子句。
無論之前異常的處理情況如何,finally
子句都會被執行。具體來講:
- 如果所有異常都被匹配並處理,則正常執行
finally
子句。 - 如果有異常未被處理,則執行
finally
子句,然後再引發異常。
比如:
try:
1/0
except IndexError:
pass
finally:
print("先执行finally,再引发异常")
先执行finally,再引发异常
Traceback (most recent call last):
...
1/0
~^~
ZeroDivisionError: division by zero
except* 語句用於處理異常組。
異常組是一種特殊的異常。其創建方法為:
ExceptionGroup(msg, excs)
其中,msg
是一個包含錯誤信息的字符串,而 excs
是包含多個異常的可迭代序列。excs
中的異常只能為 Exception
的子類。
類似 Exception
和 BaseException
,也有 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* 語句來嘗試匹配剩下的部分。
對應創建異常組時,使用的參數 msg
。
對應創建異常組時,使用的參數 excs
。它包含參數中所有異常組成的元組。
該屬性為只讀屬性。
返回一個當前異常組中、匹配異常類型 condition
的異常組成的子組。如果沒有匹配的異常,則返回 None
。
子組將和原異常組有相同的 exception 信息。
返回一個有兩個元素的元組。元組第一個元素即為 self.subgroup(condition)
,而第二個元素為剩餘的異常組成的子組。
子組將和原異常組有相同的 exception 信息。
由 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)