我想在处理列表时报告多个错误: 例如:
with MultipleExceptions.testItems(items) as value:
... process value
if value==3: raise Exception("3 err")
if value==5: raise Exception("5 err")
可能应该收集并报告块中引发异常的项目的每个值的异常(不仅仅是第一个)。
天真的实现:
class MultipleExceptions(Exception)
@staticmethod
@contextmanager
def testAllItems(items):
errs = []
for i, value in enumerate(items):
try:
yield value
except Exception e:
errs.append(i,e,value)
if errs: ... compose and raise mega exception message
...失败,因为上下文管理器只能产生一次。尚不清楚为什么这是真的。有没有办法可以多次yield?
作为发电机:
for value in MultipleExceptions.testAllItems(items):
... process value
if value==3: raise Exception("3 err")
if value==5: raise Exception("5 err")
class MultipleExceptions(Exception)
@staticmethod
def testAllItems(items):
errs = []
for i, value in enumerate(items):
try:
yield value
except Exception e:
errs.append(i,e,value)
if errs: ... compose and raise mega exception message
...失败,因为 testAllItems 中从未捕获异常。
显然我可以将我的块包装到一个函数中并将其提供给实用程序(没有yield),但是使用生成器或上下文管理器yield到一个块似乎更干净。可以吗?
不幸的是,无法从 Python 中的 raise
语句进行
resume。一旦将调用堆栈展开到特定点,就无法返回。有些语言确实允许跳回错误的堆栈帧,例如 Raku 的
resume
或 Common Lisp 的 continue
,但此功能在 Python 中不可用。
一般来说,我们在Python中倾向于做的就是积累一个错误列表并一次性抛出它们。它不像您建议的上下文管理器那样漂亮或自动,但它有效。
errors_list = []
if value == 3:
errors_list.append(Exception("3 err"))
if value == 5:
errors_list.append(Exception("5 err"))
# At the end ...
if errors_list:
raise MyCustomError(errors_list)
事实上,Python 3.11 及更高版本带有 异常组 就是这样做的。您仍然需要自己累积错误,但是
ExceptionGroup
提供了一个很好的 except*
语法来处理不同的错误类型。
errors_list = []
if value == 3:
errors_list.append(Exception("3 err"))
if value == 5:
errors_list.append(Exception("5 err"))
# At the end ...
if errors_list:
raise ExceptionGroup("Message summarizing the exceptions", errors_list)
然后,处理它们,
try:
call_possibly_failing_function()
except* SomeExceptionType as e:
# Handle all the SomeExceptionType's in the group ...
except* SomeOtherExceptionType as e:
# Handle all the SomeOtherExceptionType's in the group ...
except* Exception as e:
# Handle all uncaught exceptions ...
请注意,在这些
except*
块中(与普通 except
块相反),变量 e
的类型为 ExceptionGroup
。您可以使用 迭代该组中的 中的异常
for exc in e.exceptions:
...
...失败,因为上下文管理器只能产生一次。尚不清楚为什么这是真的。有没有办法可以多次yield?不,因为上下文管理器
不会屈服。
上下文管理器是一种围绕代码块执行设置和拆卸的方法。当进入__enter__
块时,将调用它们的
with
方法;当
__exit__
块结束时,将调用它们的
with
方法。
__exit__
可以抑制异常,但除此之外,这就是它们的全部功能。既不涉及
yield
也不涉及任何类型的隐式循环。
@contextlib.contextmanager
仅仅是创建上下文管理器的工具。它创建一个上下文管理器,其
__enter__
运行生成器直到生成,其
__exit__
恢复生成器once,并期望生成器在此之后结束。有时,这是表达设置和拆卸代码的便捷方法,但它无法改变上下文管理器协议的实际工作方式。