如何擲回和擷取多個 Python 例外狀況?Python 例外狀況群組介紹
先決條件
閱讀本節的先決條件是已經掌握 Python 例外狀況,你可以檢視Python 例外狀況介紹,以及擲回和擷取 Python 例外狀況一節來了解相關資訊。
本節所說的擷取多個 Python 例外狀況是指使用except*
陳述式擷取多個 Python 例外狀況,如果你想使用except
陳述式比對多個或任意型別的例外狀況,那麽可以檢視使用 except 陳述式比對多個或任意型別的 Python 例外狀況一段。
Python 例外狀況群組
有些時候,你可能需要同時擲回多個 Python 例外狀況來說明一種非正常情況。比如,由於沒有給出正確的值,因此未能計算出一個有效的檔案路徑,從而導致了讀取及寫入錯誤,我們可能會選擇同時擲回例外狀況ValueError
和IOError
來說明上述情況(當然,擲回一個例外狀況會更加的簡單)。
Python 提供了例外狀況群組BaseExceptionGroup
和ExceptionGroup
,可以讓開發人員一次擲回多個 Python 例外狀況,由於例外狀況群組本身也是 Python 例外狀況,因此使用raise
陳述式進行擲回沒有任何問題。
Python 例外狀況群組 BaseExceptionGroup 和 ExceptionGroup 之間的區別
Python 例外狀況群組BaseExceptionGroup
直接繼承自類別BaseException
,而ExceptionGroup
直接繼承自類別Exception
和BaseExceptionGroup
。例外狀況群組BaseExceptionGroup
可包含其他任意的 Python 例外狀況,而ExceptionGroup
僅支援包含Exception
或繼承自Exception
的例外狀況。
建立 Python 例外狀況群組
Python 例外狀況群組BaseExceptionGroup
和ExceptionGroup
提供了以下建構子用於建立一個例外狀況群組的執行個體。
BaseExceptionGroup(message, exceptions)
ExceptionGroup(message, exceptions)
- message 參數
message
參數是一個用於說明 Python 例外狀況群組的字串。- exceptions 參數
exceptions
參數是一個 Python 序列物件(比如,串列,元組),用於包含需要同時擲回的多個例外狀況。如果exceptions
對應的序列物件未包含任何 Python 例外狀況,那麽將引發例外狀況ValueError
。對於ExceptionGroup
,如果exceptions
對應的序列物件所包含的例外狀況並非Exception
或繼承自Exception
,那麽將引發例外狀況TypeError: Cannot nest … in an ExceptionGroup
。
下面的程式碼,將一個BaseExceptionGroup
例外狀況群組包含在了另一個BaseExceptionGroup
例外狀況群組中,並將BaseException
例外狀況包含在ExceptionGroup
例外狀況群組中,當然,這導致了最終的錯誤。
# 在例外狀況群組中包含例外狀況群組,並使用例外狀況 BaseException
BaseExceptionGroup(
'例外狀況群組 BaseExceptionGroup',
[BaseExceptionGroup('包含在例外狀況群組中的例外狀況群組', [BaseException()])]
)
# 在例外狀況群組 ExceptionGroup 中使用例外狀況 BaseException
ExceptionGroup(
'例外狀況群組 ExceptionGroup',
# ERROR 不能包含 BaseException
[Exception(), BaseException()]
)
TypeError: Cannot nest BaseExceptions in an ExceptionGroup
Python 例外狀況群組 BaseExceptionGroup 的建構子可能傳回 ExceptionGroup 物件
在建立 Python 例外狀況群組BaseExceptionGroup
的執行個體時,如果參數exceptions
中包含的例外狀況的型別均為Exception
或其衍生類別,那麽BaseExceptionGroup
的建構子將傳回一個ExceptionGroup
物件,而非BaseExceptionGroup
物件。
因此,無論何時,你都可以使用BaseExceptionGroup
的建構子來建立 Python 例外狀況群組物件,而不用關註所包含的 Python 例外狀況的型別。
由於例外狀況SystemExit
直接繼承自BaseException
,因此,建構子傳回一個BaseExceptionGroup
物件。由於例外狀況NameError
直接繼承自Exception
,因此,建構子傳回一個ExceptionGroup
物件。
# SystemExit 直接繼承自 BaseException
err = BaseExceptionGroup('例外狀況群組 BaseExceptionGroup', (SystemExit(),))
print(type(err))
# NameError 直接繼承自 Exception
err = BaseExceptionGroup('例外狀況群組 ExceptionGroup', (NameError(),))
print(type(err))
<class 'BaseExceptionGroup'>
<class 'ExceptionGroup'>
通過 Python 例外狀況群組擲回(引發)多個例外狀況
如上所述,Python 例外狀況群組BaseExceptionGroup
和ExceptionGroup
本身也是 Python 例外狀況,可以通過raise
陳述式來擲回(引發)他們。在這一點上,例外狀況群組和普通例外狀況沒有區別,只不過例外狀況群組包含了一組其他的 Python 例外狀況。
在下面的範例中,我們擲回了一個例外狀況群組,他包含了 Python 例外狀況ValueError
和NameError
。
# 擲回包含例外狀況 ValueError 和 NameError 的例外狀況群組
raise ExceptionGroup('糟糕,又錯了', (ValueError(), NameError()))
ExceptionGroup: 糟糕,又錯了 (2 sub-exceptions)
…
ValueError
…
NameError
擷取處理 Python 例外狀況群組
對於 Python 例外狀況群組BaseExceptionGroup
和ExceptionGroup
,你可以通過try…except
來擷取處理他們(作為一個整體),就像其他普通例外狀況一樣,當然,這可能使得使用 Python 例外狀況群組變得毫無意義。
# 將 Python 例外狀況群組作為一個整體進行擷取
try:
raise ExceptionGroup('毫無意義', (IOError(),))
except BaseExceptionGroup as err:
print(f'擷取的例外狀況型別為:{type(err)}')
擷取的例外狀況型別為:<class 'ExceptionGroup'>
使用 except* 陳述式擷取處理 Python 例外狀況群組中的一個或多個例外狀況
使用except*
陳述式,可以擷取 Python 例外狀況群組中某些的例外狀況,這包括巢狀的例外狀況(例外狀況群組中的例外狀況群組中的例外狀況),只需給出例外狀況的型別即可。從語法形式上講,except*
陳述式和except
陳述式是類似的,他接受as
關鍵字,可使用元組指定多個 Python 例外狀況型別,可使用sys
模組的exception
函式取得擷取到的例外狀況(事實上,except*
陳述式中取得的是例外狀況群組),不同的是,except*
陳述式無法通過省略型別來比對例外狀況群組中的任意例外狀況。
try:
<try-block>
except* <exception-1>:
<except-block-1>
…
except* <exception-N>:
<except-block-N>
Python 例外狀況群組中的例外狀況最多只能與一個 except* 陳述式成功比對
當 Python 例外狀況群組中的某個例外狀況與某個except*
陳述式比對成功後,該例外狀況不再參與剩余except*
陳述式的比對,這表示except*
陳述式將依次執行,以比對例外狀況群組中尚未比對成功的例外狀況。
與 except* 陳述式成功比對的例外狀況將被轉換為 Python 例外狀況群組
如果 Python 例外狀況群組中的某些例外狀況與except*
陳述式相符,那麽這些例外狀況將被轉換為一個新的 Python 例外狀況群組,以供except*
陳述式對應的程式碼使用。新例外狀況群組的型別可能是BaseExceptionGroup
或ExceptionGroup
,這需要根據比對成功的例外狀況的型別來決定,新例外狀況群組的message
屬性與被擲回例外狀況群組的message
屬性的傳回值相同,新例外狀況群組的exceptions
屬性包含了比對成功的一個或多個 Python 例外狀況。
在下面的範例中,第一個except*
陳述式使用元組比對到了例外狀況群組中的NameError
和ValueError
,由於都繼承自Exception
,因此他們會被轉換為一個ExceptionGroup
物件,第二個except*
陳述式比對到了例外狀況群組中的BaseException
,他將被轉換為一個BaseExceptionGroup
物件。
try:
raise BaseExceptionGroup('擷取', [NameError(), ValueError(), BaseException()])
except* (NameError, ValueError) as err:
# 轉換的例外狀況群組的型別為 ExceptionGroup
print(f'例外狀況型別為:{type(err)},{err.message} {err.exceptions}')
except* BaseException:
# 轉換的例外狀況群組的型別為 BaseExceptionGroup
import sys
# 通過 exception 函式取得轉換的例外狀況群組
err = sys.exception()
print(f'例外狀況型別為:{type(err)},{err.message} {err.exceptions}')
例外狀況型別為:<class 'ExceptionGroup'>,擷取 (NameError(), ValueError())
例外狀況型別為:<class 'BaseExceptionGroup'>,擷取 (BaseException(),)
except* 陳述式可以與 Python 例外狀況群組中某種型別的所有例外狀況成功比對
except*
陳述式將比對 Python 例外狀況群組中指定型別(或繼承自該型別)的所有例外狀況,除非他們已經與之前其他的except*
陳述式相符。
下面的第一個except*
陳述式,將與例外狀況群組中的兩個IOError
,FileNotFoundError
相符,第二個except*
陳述式,將只能與剩下的兩個Exception
相符。
try:
raise BaseExceptionGroup('比對多個例外狀況', [Exception(), Exception(), IOError(), IOError(), FileNotFoundError()])
except* IOError as err:
# 比對 IOError,FileNotFoundError
print(err.exceptions)
except* Exception as err:
# 只能比對到 Exception
print(err.exceptions)
# IOError 是 OSError 的別名
(OSError(), OSError(), FileNotFoundError())
(Exception(), Exception())
Python 例外狀況群組中未能成功比對的例外狀況將被重新擲回
當 Python 例外狀況群組中的某些例外狀況,包括巢狀的例外狀況,未能與任何except*
陳述式成功比對時,這些例外狀況將組合成一個新的例外狀況群組被重新擲回,並保持原有的巢狀結構(如果有的話),新例外狀況群組的message
屬性為空字串,型別(BaseExceptionGroup
或ExceptionGroup
)由比對失敗的 Python 例外狀況決定。
下面的範例,例外狀況群組中的例外狀況NameError
和OSError
不與except*
陳述式相符,因此他們合併為新的例外狀況群組被重新擲回。
try:
# 例外狀況 NameError,OSError 會被重新擲回
try:
raise BaseExceptionGroup(
'重新擲回', (
NameError(), ValueError(),
BaseExceptionGroup('巢狀的例外狀況', [OSError(), ValueError()])
))
except* ValueError:
# 兩個 ValueError 都會被擷取
pass
except BaseExceptionGroup as err:
# 例外狀況群組包含了 NameError 和巢狀的 OSError
print(f'被重新擲回的例外狀況:{type(err)} {err.exceptions}')
被重新擲回的例外狀況:<class 'ExceptionGroup'> (NameError(), ExceptionGroup('巢狀的例外狀況', [OSError()]))
except* 陳述式不能比對型別為 BaseExceptionGroup 的例外狀況
與except
陳述式不同,except*
陳述式不能比對型別為BaseExceptionGroup
的例外狀況,或繼承自BaseExceptionGroup
的例外狀況。
在下面的程式碼中,我們嘗試使用except*
陳述式比對ExceptionGroup
,這會導致例外狀況TypeError
。
try:
raise BaseExceptionGroup('例外狀況群組', [
ExceptionGroup('子例外狀況群組', [ValueError(), NameError()])
])
# ERROR 不能比對 ExceptionGroup
except* ExceptionGroup:
pass
TypeError: catching ExceptionGroup with except* is not allowed. Use except instead.
except* 陳述式不能與 except 陳述式同時使用
在使用try
陳述式之後,不能混合使用except*
陳述式和except
陳述式,否則會引發例外狀況SyntaxError: cannot have both 'except' and 'except*' on the same 'try'
。
# ERROR 無法混合使用 except* 和 except
try:
raise ExceptionGroup('混合使用', [SystemExit()])
except* SystemExit:
pass
except:
pass
SyntaxError: cannot have both 'except' and 'except*' on the same 'try'
無法在 except* 陳述式的相關程式碼中使用跳躍陳述式
在except*
陳述式的相關程式碼中,無法使用break
,continue
和return
陳述式,否則將導致例外狀況SyntaxError: 'break', 'continue' and 'return' cannot appear in an except* block
。
def jump():
try:
raise BaseExceptionGroup('跳躍', [NameError()])
except* NameError:
# ERROR 不能使用跳躍陳述式
return
jump()
SyntaxError: 'break', 'continue' and 'return' cannot appear in an except* block
使用 except* 陳述式擷取處理 Python 普通例外狀況
使用except*
陳述式,不僅可以擷取 Python 例外狀況群組中的例外狀況,也可以擷取被擲回的普通例外狀況,只不過這些普通例外狀況將被轉換為一個例外狀況群組,例外狀況群組的message
屬性的值為空字串。
在下面的程式碼中,我們引發了一個普通的型別為NameError
的例外狀況,該例外狀況將被轉換為型別為ExceptionGroup
的例外狀況群組。
# 擷取普通的例外狀況
try:
raise NameError()
except* NameError as err:
# NameError 被轉換為例外狀況群組
print(f'{type(err)} 訊息:“{err.message}”,例外狀況:{err.exceptions}')
<class 'ExceptionGroup'> 訊息:“”,例外狀況:(NameError(),)
取得 Python 例外狀況群組的資訊
通過 Python 例外狀況群組BaseExceptionGroup
和ExceptionGroup
的屬性message
,可以取得例外狀況群組的資訊,即建構子的message
參數的值。
取得 Python 例外狀況群組中的所有例外狀況
Python 例外狀況群組BaseExceptionGroup
和ExceptionGroup
的屬性exceptions
是一個 Python 元組,他包含了該例外狀況群組中的所有例外狀況。
try:
# 由於所包含的例外狀況均繼承自 Exception,因此會得到一個 ExceptionGroup 物件
raise BaseExceptionGroup(
'這僅僅是一個測試!',
[NameError(), ValueError()]
)
except ExceptionGroup as err:
# 顯示例外狀況群組的相關內容
print(f'來自例外狀況群組的資訊:{err.message}')
print(f'例外狀況群組中的例外狀況:{err.exceptions}')
來自例外狀況群組的訊息:這僅僅是一個測試!
例外狀況群組中的例外狀況:(NameError(), ValueError())
取得 Python 例外狀況群組中符合條件的例外狀況
Python 例外狀況群組BaseExceptionGroup
和ExceptionGroup
物件的subgroup
方法,可用於取得例外狀況群組中符合指定條件的例外狀況(包括巢狀的例外狀況),並作為一個新的例外狀況群組(會保持原有巢狀結構和message
屬性值)傳回,例外狀況群組的型別由取得的例外狀況決定,這種效果類似於通過except*
陳述式擷取例外狀況。如果例外狀況群組中沒有符合條件的例外狀況,那麽subgroup
方法傳回空值None
。
與subgroup
方法類似,Python 例外狀況群組BaseExceptionGroup
和ExceptionGroup
物件的split
方法,同樣可用於取得例外狀況群組中符合指定條件的例外狀況,只不過split
方法還會計算例外狀況群組中不符合條件的例外狀況,並傳回元組(match,rest)
,其中match
等同於subgroup
方法的計算結果,rest
為包含不符合條件的例外狀況的例外狀況群組(會保持原有巢狀結構和message
屬性值),如果沒有不符合條件的例外狀況,那麽rest
為空值None
。
baseexceptiongroup|exceptiongroup.subgroup(condition)
baseexceptiongroup|exceptiongroup.split(condition)
- condition 參數
condition
參數是需要取得的 Python 例外狀況的型別,或包含需要取得的 Python 例外狀況的型別的元組,或用於判斷例外狀況是否符合條件的函式或方法(傳回True
表示符合條件)。與使用except*
陳述式不同,condition
參數可以是BaseExceptionGroup
的衍生類別。
在下面的程式碼中,由於例外狀況群組err
不包含例外狀況AttributeError
,因此err.subgroup(AttributeError)
傳回了空值None
,split
方法使用了函式get_error
來判斷例外狀況是否符合條件。
err = ExceptionGroup('取得例外狀況', (
NameError(), ValueError(),
BaseExceptionGroup('子例外狀況群組', [OSError(), NameError()])
))
# 傳回 None
print(err.subgroup(AttributeError))
# 取得例外狀況群組中的 NameError 和 ValueError
group = err.subgroup((NameError, ValueError))
print(f'{group.message} {group.exceptions}')
# 判斷例外狀況是否符合條件的函式 get_error
def get_error(err):
return type(err) in (NameError, ValueError)
# 通過函式取得例外狀況群組中的 NameError 和 ValueError,以及剩余例外狀況
m, r = err.split(get_error)
print(f'{m.message} {m.exceptions} {r.message} {r.exceptions}')
None
取得例外狀況 (NameError(), ValueError(), ExceptionGroup('子例外狀況群組', [NameError()]))
取得例外狀況 (NameError(), ValueError(), ExceptionGroup('子例外狀況群組', [NameError()])) 取得例外狀況 (ExceptionGroup('子例外狀況群組', [OSError()]),)
根據 Python 例外狀況群組建立新的例外狀況群組
使用 Python 例外狀況群組BaseExceptionGroup
和ExceptionGroup
物件的derive
方法,可依據原有例外狀況群組建立新的例外狀況群組,新的例外狀況群組和原有例外狀況群組可以包含不同的例外狀況,而其他內容會被保留,比如,__traceback__
,__cause__
,__context__
和__notes__
。
derive
方法會被subgroup
和split
方法使用,以實作其功能。
baseexceptiongroup|exceptiongroup.derive(excs)
- excs 參數
excs
參數是一個包含了例外狀況的 Python 序列物件,這些例外狀況將被包含在新建立的例外狀況群組中。
在下面的範例中,我們通過derive
方法建立了新的例外狀況群組,並指定ValueError
為其包含的例外狀況。
err = ExceptionGroup('例外狀況群組', [NameError()])
new_err = err.derive([ValueError()])
# 顯示原有例外狀況群組和新例外狀況群組
print(f'{err.message} {err.exceptions}')
print(f'{new_err.message} {new_err.exceptions}')
例外狀況群組 (NameError(),)
例外狀況群組 (ValueError(),)