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?
IritHello,
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