如何抛出和捕获多个异常?Python 异常组介绍
关注 1421
前提
阅读本节的前提是已经掌握 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(),)