One of the motivations for introducing ExceptionGroup as a builtin is so that we won't have a different custom version in each library that needs it. So if you are writing a library the needs to raise multiple exceptions, and then you decide to call Trio, you don't need to translate Trio's MultiError into your own exception group type, because everybody uses the builtin. And your users don't need to learn how your particular exception group works because they know that you are using the builtin one. I see the aesthetic value of your suggestion, but does it have practical advantages in light of the above? Irit On Sat, Mar 27, 2021 at 10:31 AM Paul Sokolovsky <pmiscml@gmail.com> wrote:
Hello,
On Fri, 26 Mar 2021 16:19:26 -0700 Guido van Rossum <guido@python.org> wrote:
Everyone,
Given the resounding silence I'm inclined to submit this to the Steering Council. While I'm technically a co-author, Irit has done almost all the work, and she's done a great job. If there are no further issues I'll send this SC-wards on Monday.
One issue with PEP654 is that it introduces pretty adhoc and complex-semantics concept (ExceptionGroup) on the language level. Here's an idea (maybe duplicate) on how to introduce a much simpler, and more generic concept on the language level, and let particular frameworks to introduce (and elaborate without further changing the language) adhoc concept they need.
So, let's look how the usual "except MyExc as e" works: it performs "isinstance(e0, MyExc)" operation, where e0 is incoming exception (roughly, sys.exc_info[1]), and if it returns True, then assigns e0 to the "e" variable and executes handler body. "isinstance(e0, MyExc)" is formally known as an "exception filter".
As we see, currently Python hardcodes isinstance() as exception filter. The idea is to allow to use an explicit exception filter. Let's reuse the same "except *" syntax to specify it. Also, it's more flexible instead of returning True/False from filter, to return either None (filter didn't match), or an exception object to make available to handler (which in general may be different than passed to the filter). With this, ExceptionGroup usecases should be covered.
Examples:
1. Current implicit exception filter is equivalent to:
def implicit(e0, excs): # one or tuple, as usual if isinstance(e0, excs): return e0 return None
try: 1/0 except *implicit(ZeroDivisionError) as e: print(e)
2. Allow to catch main or chained exception (context manager example from PEP)
def chained(e, excs): while e: if isinstance(e, excs): return e e = e.__cause__ # Simplified, should consider __context__ too
try: tempfile.TemporaryDirectory(...) except *chained(OSError) as e: print(e)
3. Rough example of ExceptionGroup functionality (now not a language builtin, just implemented by framework(s) which need it, or as a separate module):
class ExceptionGroup:
...
@staticmethod def match(e0, excs): cur, rest = e0.split_by_types(excs) # That's how we allow an exception handler to re-raise either an # original group in full or just "unhandled" exception in the # group (or anything) - everything should be passed via # exception attributes (or computed by methods). cur.org = e0 cur.rest = rest return cur
try: ... except *ExceptionGroup.match((TypeError, ValueError)) as e: # try to handle a subgroup with (TypeError, ValueError) here ... # now reraise a subgroup with unhandled exceptions from the # original group raise e.rest