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