On Wed, 3 Mar 2021 at 12:37, Irit Katriel <iritkatriel@googlemail.com> wrote:
This is covered the PEP, but TL;DR: If we could make "except KeyboardInterrupt" catch BaseExceptionGroup([KeyboardInterrupt]) in a reasonably backwards compatible way then we wouldn't need except*. [...] As you noted, no library is under any obligation to wrap KeyboardInterrupts into the exception groups it raises. You may decide it's a bad idea and not do it. What we are discussing here is what the language should make possible. We agree that wrapping a BaseException by an Exception is something we should definitely block. When it's wrapping a BaseException by another, new BaseException type, in my view that's ok. You may have a bug where you don't catch an exception you want to catch, because you are using a new API incorrectly. But you won't have bugs where you swallow an exception that you didn't swallow before.
Thanks for the explanation. I understand your point here, and I see what you're saying. But I have a couple of questions still: 1. Having now read the PEP, I don't actually see a use case for grouping BaseExceptions. Why would anyone catch KeyboardInterrupt or SystemExit and wrap them in a BaseExceptionGroup anyway? It seems to me that the right thing to do, even in async or parallel code, is to just propogate the KeyboardInterrupt/SystemExit up to the main program. Losing a ValueError that happened at the exact same time as the user pressed Ctrl-C seems like it's not going to be a problem in practice... 2. Given the above, why even have a means of grouping BaseExceptions at all? Why not just have ExceptionGroup that can only catch instances of Exception? If there really *was* a low-level case where some code absolutely had to (temporarily) group KeyboardInterrupt, for example, it could be temporarily wrapped: class IGotInterrupted(Exception): def __init__(self, exc): self.exc = exc def might_be_interrupted(): try: critical_stuff() except KeyboardInterrupt as exc: raise IGotInterrupted(exc) def funky_parallel_stuff(): try: do_in_parallel(might_be_interrupted) except *IGotInterrupted as e: # Please excuse the fact that I don't know the # correct way to re-raise here. Might need raise from. raise e.exc # Do we also need to consider an *ungrouped* IGotInterrupted? # That's for the library to decide, and is why not letting it escape is a good thing... That would be appropriate for really low-level code, and it would be a fairly obvious anti-pattern for an exception class like IGotInterrupted to "escape" so that user code would ever see it. I guess the argument for grouping BaseExceptions is "why make it necessary to do complicated wrapping like this?" To which my answer is "because it should almost never be something you do, and so why add support for it when it can be done *without* needing explicit support in the PEP?" And by not having stdlib support for wrapping BaseExceptions, we're signalling that we don't think people should be doing it casually... Paul