如何擲回和擷取多個 Python 例外狀況?Python 例外狀況群組介紹

閱讀 22:33·字數 6767·發佈 
Youtube 頻道
訂閱 133

先決條件

閱讀本節的先決條件是已經掌握 Python 例外狀況,你可以檢視Python 例外狀況介紹,以及擲回和擷取 Python 例外狀況一節來了解相關資訊。

本節所說的擷取多個 Python 例外狀況是指使用except*陳述式擷取多個 Python 例外狀況,如果你想使用except陳述式比對多個或任意型別的例外狀況,那麽可以檢視使用 except 陳述式比對多個或任意型別的 Python 例外狀況一段。

Python 例外狀況群組

有些時候,你可能需要同時擲回多個 Python 例外狀況來說明一種非正常情況。比如,由於沒有給出正確的值,因此未能計算出一個有效的檔案路徑,從而導致了讀取及寫入錯誤,我們可能會選擇同時擲回例外狀況ValueErrorIOError來說明上述情況(當然,擲回一個例外狀況會更加的簡單)。

Python 提供了例外狀況群組BaseExceptionGroupExceptionGroup,可以讓開發人員一次擲回多個 Python 例外狀況,由於例外狀況群組本身也是 Python 例外狀況,因此使用raise陳述式進行擲回沒有任何問題。

Python 例外狀況群組 BaseExceptionGroup 和 ExceptionGroup 之間的區別

Python 例外狀況群組BaseExceptionGroup直接繼承自類別BaseException,而ExceptionGroup直接繼承自類別ExceptionBaseExceptionGroup。例外狀況群組BaseExceptionGroup可包含其他任意的 Python 例外狀況,而ExceptionGroup僅支援包含Exception或繼承自Exception的例外狀況。

建立 Python 例外狀況群組

Python 例外狀況群組BaseExceptionGroupExceptionGroup提供了以下建構子用於建立一個例外狀況群組的執行個體。

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例外狀況群組中,當然,這導致了最終的錯誤。

base.py
# 在例外狀況群組中包含例外狀況群組,並使用例外狀況 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物件。

base_exception.py
# SystemExit 直接繼承自 BaseException
err = BaseExceptionGroup('例外狀況群組 BaseExceptionGroup', (SystemExit(),))
print(type(err))

# NameError 直接繼承自 Exception err = BaseExceptionGroup('例外狀況群組 ExceptionGroup', (NameError(),)) print(type(err))
<class 'BaseExceptionGroup'>
<class 'ExceptionGroup'>

通過 Python 例外狀況群組擲回(引發)多個例外狀況

如上所述,Python 例外狀況群組BaseExceptionGroupExceptionGroup本身也是 Python 例外狀況,可以通過raise陳述式來擲回(引發)他們。在這一點上,例外狀況群組和普通例外狀況沒有區別,只不過例外狀況群組包含了一組其他的 Python 例外狀況。

在下面的範例中,我們擲回了一個例外狀況群組,他包含了 Python 例外狀況ValueErrorNameError

raise.py
# 擲回包含例外狀況 ValueError 和 NameError 的例外狀況群組
raise ExceptionGroup('糟糕,又錯了', (ValueError(), NameError()))
ExceptionGroup: 糟糕,又錯了 (2 sub-exceptions)

ValueError

NameError

擷取處理 Python 例外狀況群組

對於 Python 例外狀況群組BaseExceptionGroupExceptionGroup,你可以通過try…except來擷取處理他們(作為一個整體),就像其他普通例外狀況一樣,當然,這可能使得使用 Python 例外狀況群組變得毫無意義。

catch.py
# 將 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*陳述式對應的程式碼使用。新例外狀況群組的型別可能是BaseExceptionGroupExceptionGroup,這需要根據比對成功的例外狀況的型別來決定,新例外狀況群組的message屬性與被擲回例外狀況群組的message屬性的傳回值相同,新例外狀況群組的exceptions屬性包含了比對成功的一個或多個 Python 例外狀況。

在下面的範例中,第一個except*陳述式使用元組比對到了例外狀況群組中的NameErrorValueError,由於都繼承自Exception,因此他們會被轉換為一個ExceptionGroup物件,第二個except*陳述式比對到了例外狀況群組中的BaseException,他將被轉換為一個BaseExceptionGroup物件。

catch.py
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*陳述式,將與例外狀況群組中的兩個IOErrorFileNotFoundError相符,第二個except*陳述式,將只能與剩下的兩個Exception相符。

catch.py
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屬性為空字串,型別(BaseExceptionGroupExceptionGroup)由比對失敗的 Python 例外狀況決定。

下面的範例,例外狀況群組中的例外狀況NameErrorOSError不與except*陳述式相符,因此他們合併為新的例外狀況群組被重新擲回。

catch_raise.py
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

catch_group.py
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'

catch_mix.py
# 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*陳述式的相關程式碼中,無法使用breakcontinuereturn陳述式,否則將導致例外狀況SyntaxError: 'break', 'continue' and 'return' cannot appear in an except* block

jump.py
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的例外狀況群組。

catch_common.py
# 擷取普通的例外狀況
try:
	raise NameError()
except* NameError as err:
	# NameError 被轉換為例外狀況群組
	print(f'{type(err)} 訊息:“{err.message}”,例外狀況:{err.exceptions}')
<class 'ExceptionGroup'> 訊息:“”,例外狀況:(NameError(),)

取得 Python 例外狀況群組的資訊

通過 Python 例外狀況群組BaseExceptionGroupExceptionGroup的屬性message,可以取得例外狀況群組的資訊,即建構子的message參數的值。

取得 Python 例外狀況群組中的所有例外狀況

Python 例外狀況群組BaseExceptionGroupExceptionGroup的屬性exceptions是一個 Python 元組,他包含了該例外狀況群組中的所有例外狀況。

info.py
try:
	# 由於所包含的例外狀況均繼承自 Exception,因此會得到一個 ExceptionGroup 物件
	raise BaseExceptionGroup(
		'這僅僅是一個測試!',
		[NameError(), ValueError()]
	)
except ExceptionGroup as err:
	# 顯示例外狀況群組的相關內容
	print(f'來自例外狀況群組的資訊:{err.message}')
	print(f'例外狀況群組中的例外狀況:{err.exceptions}')
來自例外狀況群組的訊息:這僅僅是一個測試!
例外狀況群組中的例外狀況:(NameError(), ValueError())

取得 Python 例外狀況群組中符合條件的例外狀況

Python 例外狀況群組BaseExceptionGroupExceptionGroup物件的subgroup方法,可用於取得例外狀況群組中符合指定條件的例外狀況(包括巢狀的例外狀況),並作為一個新的例外狀況群組(會保持原有巢狀結構和message屬性值)傳回,例外狀況群組的型別由取得的例外狀況決定,這種效果類似於通過except*陳述式擷取例外狀況。如果例外狀況群組中沒有符合條件的例外狀況,那麽subgroup方法傳回空值None

subgroup方法類似,Python 例外狀況群組BaseExceptionGroupExceptionGroup物件的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)傳回了空值Nonesplit方法使用了函式get_error來判斷例外狀況是否符合條件。

get.py
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 例外狀況群組BaseExceptionGroupExceptionGroup物件的derive方法,可依據原有例外狀況群組建立新的例外狀況群組,新的例外狀況群組和原有例外狀況群組可以包含不同的例外狀況,而其他內容會被保留,比如,__traceback____cause____context____notes__

derive方法會被subgroupsplit方法使用,以實作其功能。

baseexceptiongroup|exceptiongroup.derive(excs)

excs 參數

excs參數是一個包含了例外狀況的 Python 序列物件,這些例外狀況將被包含在新建立的例外狀況群組中。

在下面的範例中,我們通過derive方法建立了新的例外狀況群組,並指定ValueError為其包含的例外狀況。

new.py
err = ExceptionGroup('例外狀況群組', [NameError()])
new_err = err.derive([ValueError()])

# 顯示原有例外狀況群組和新例外狀況群組 print(f'{err.message} {err.exceptions}') print(f'{new_err.message} {new_err.exceptions}')
例外狀況群組 (NameError(),)
例外狀況群組 (ValueError(),)

程式碼

src/zh-hant/exceptions/groups·codebeatme/python·GitHub