如何抛出和捕获多个 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(),)