![](https://secure.gravatar.com/avatar/047f2332cde3730f1ed661eebb0c5686.jpg?s=120&d=mm&r=g)
So is "fail-fast if you forget to handle an ExceptionGroup" really a feature? (Do we call this out in the PEP?) We may believe that "except Exception" is an abuse, but it is too common to dismiss out of hand. I think if some app has e.g. a main loop where they repeatedly do something that may fail in many ways (e.g. handle a web request), catch all errors and then just log the error and continue from the top, it's a better experience if it logs "ExceptionGroup: <message> [<list of subexceptions>]" than if it crashes. There's also code that catches Exception, logs the error and some relevant data, and then re-raises it. The logged error may go to a more specialized destination than the generic traceback, and the log message may contain additional data that's only available in that stack frame. So I think there are enough serious use cases that we should do what's best for those use cases, and not try to lecture users about abuse of the idiom. I don't know what we would have done if we were building Python from scratch. Possibly we would not have BaseException at all, and the whole mess would go away. (But there are some good reasons why we introduced BaseException, so I don't know that that would really be a better overall experience.) --Guido On Thu, Feb 25, 2021 at 2:46 AM Irit Katriel <iritkatriel@googlemail.com> wrote:
On Thu, Feb 25, 2021 at 5:59 AM Guido van Rossum <guido@python.org> wrote:
Here's a potentially alternative plan, which is also complex, but doesn't require asyncio or other use cases to define special classes. Let's define two exceptions, BaseExceptionGroup which wraps BaseException instances, and ExceptionGroup which only wraps Exception instances. (Names to be bikeshedded.) They could share a constructor (always invoked via BaseExceptionGroup) which chooses the right class depending on whether there are any non-Exception instances being wrapped -- this would do the right thing for split() and subgroup() and re-raising unhandled exceptions.
Then 'except Exception:' would catch ExceptionGroup but not BaseExceptionGroup, so if a group wraps e.g. KeyboardError it wouldn't be caught (even if there's also e.g. a ValueError among the wrapped errors).
Pseudo-code:
class BaseExceptionGroup(BaseException): def __new__(cls, msg, errors): if cls is BaseExceptionGroup and all(isinstance(e, Exception) for e in errors): cls = ExceptionGroup return BaseException.__new__(cls, msg, errors)
class ExceptionGroup(Exception, BaseExceptionGroup): pass
This could be a valid compromise.
We keep the ability to wrap any exception, while we lose the "fail-fast if you forget to handle an ExceptionGroup" feature, which was intended as a kindness towards those who abuse "except Exception".
If we adopt this solution then letting an ExceptionGroup escape from code that is not supposed to raise it, is not a fatal error, it's just some exception like any other. So there is no longer a distinction between code that raises ExceptionGroups and code that doesn't. Any code can propagate them, like any code can raise any other exception. Does this mean that more code needs to be aware of the possibility of them showing up? Is that a problem? Maybe this a simpler state of affairs overall.
What would we have done here if we were building Python from scratch?
Irit
-- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-c...>