URLhttps://learnscript.net/zh/python/exceptions/exception-groups/
    复制链接转到说明  示例

    如何抛出和捕获多个异常?Python 异常组介绍

    我被代码海扁署名-非商业-禁演绎
    阅读 20:01·字数 6008·发布 

    前提

    阅读本节的前提是已经掌握 Python 异常,你可以查看Python 异常介绍,以及抛出和捕获异常一节来了解相关信息。

    本节所说的捕获多个 Python 异常是指使用except*语句捕获多个 Python 异常,如果你想使用except语句匹配多个或任意类型的异常,那么可以查看匹配多个或任意类型的 Python 异常一段。

    Python 异常组

    有些时候,你可能需要同时抛出多个异常来说明一种非正常情况。比如,由于没有给出正确的值,因此未能计算出一个有效的文件路径,从而导致了读写错误,我们可能会选择同时抛出异常ValueErrorIOError来说明上述情况(当然,抛出一个异常会更加的简单)。

    Python 提供了异常组BaseExceptionGroupExceptionGroup,可以让开发人员一次抛出多个异常,由于异常组本身也是异常,因此使用raise语句进行抛出没有任何问题。

    异常组 BaseExceptionGroup 和 ExceptionGroup 之间的区别

    异常组BaseExceptionGroup直接继承自类BaseException,而ExceptionGroup直接继承自类ExceptionBaseExceptionGroup。异常组BaseExceptionGroup可包含其他任意的异常,而ExceptionGroup仅支持包含Exception或继承自Exception的异常。

    创建 Python 异常组

    异常组BaseExceptionGroupExceptionGroup提供了以下构造器用于创建一个异常组的实例。

    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异常组中,当然,这导致了最终的错误。

    base.py
    # 在异常组中包含异常组,并使用异常 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对象。

    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 异常组抛出(引发)多个异常

    如上所述,异常组BaseExceptionGroupExceptionGroup本身也是异常,可以通过raise语句来抛出(引发)他们。在这一点上,异常组和普通异常没有区别,只不过异常组包含了一组其他的异常。

    在下面的示例中,我们抛出了一个异常组,他包含了异常ValueErrorNameError

    raise.py
    # 抛出包含异常 ValueError 和 NameError 的异常组
    raise ExceptionGroup('糟糕,又错了', (ValueError(), NameError()))
    ExceptionGroup: 糟糕,又错了 (2 sub-exceptions)

    ValueError

    NameError

    捕获处理 Python 异常组

    对于异常组BaseExceptionGroupExceptionGroup,你可以通过try…except来捕获处理他们(作为一个整体),就像其他普通异常一样,当然,这可能使得使用异常组变得毫无意义。

    catch.py
    # 将 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*语句对应的代码使用。新异常组的类型可能是BaseExceptionGroupExceptionGroup,这需要根据匹配成功的异常的类型来决定,新异常组的message属性与被抛出异常组的message属性的返回值相同,新异常组的exceptions属性包含了匹配成功的一个或多个异常。

    在下面的示例中,第一个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* 语句可以与异常组中某种类型的所有异常成功匹配

    except*语句将匹配异常组中指定类型(或继承自该类型)的所有异常,除非他们已经与之前其他的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())

    异常组中未能成功匹配的异常将被重新抛出

    当异常组中的某些异常,包括嵌套的异常,未能与任何except*语句成功匹配时,这些异常将组合成一个新的异常组被重新抛出,并保持原有的嵌套结构(如果有的话),新异常组的message属性为空字符串,类型(BaseExceptionGroupExceptionGroup)由匹配失败的异常决定。

    下面的示例,异常组中的异常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

    捕获处理 Python 普通异常

    使用except*语句,不仅可以捕获异常组中的异常,也可以捕获被抛出的普通异常,只不过这些普通异常将被转换为一个异常组,异常组的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 异常组的信息

    通过异常组BaseExceptionGroupExceptionGroup的属性message,可以获取异常组的信息,即构造器的message参数的值。

    获取 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 异常组中符合条件的异常

    异常组BaseExceptionGroupExceptionGroup对象的subgroup方法,可用于获取异常组中符合指定条件的异常(包括嵌套的异常),并作为一个新的异常组(会保持原有嵌套结构和message属性值)返回,异常组的类型由获取的异常决定,这种效果类似于通过except*语句捕获异常。如果异常组中没有符合条件的异常,那么subgroup方法返回空值None

    subgroup方法类似,异常组BaseExceptionGroupExceptionGroup对象的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)返回了空值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 异常组创建新的异常组

    使用异常组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/exceptions/groups·codebeatme/python·GitHub