Hi Paul, On Sat, Mar 27, 2021 at 6:00 PM Paul Sokolovsky <pmiscml@gmail.com> wrote:
It definitely feels like a lot of effort went into devising and polishing ExceptionGroup's and except*, thanks. But I'm not sure if you gentlemen come up with the "ultimate" way to deal with multiple errors,
I've been mistaken for a man before, but no-one has ever confused me for gentle. I'll take that as a compliment.
which deserves being codified on the language level (vs be added as pluggable means indeed).
Pluggable is not without its problems. I'm all in favor of you developing this idea and proposing an alternative. As I said before, you just need to answer two questions: 1. show a limitation of our approach (the contrived flat-set is not one - see below) 2. describe how a pluggable approach works for that case
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)
What if the user code raised an OSError too, so now that exception group has more than one?
I don't see how that's different from PEP654's ExceptionGroup's case. It's just in ExceptionGroup, there would be 2 (unrelated?) OSError's, while using normal chaining rules, there's at least context info which is preserved.
It's different because your "match" function returns a single exception (the first one that is of OSError type). Any further OSErrors will be reraised. The PEP's except* knows how to match multiple exceptions of the relevant type.
I am interested to know (1) what limitations you see in the ExceptionGroup type we are proposing (in what sense is it special-purposed?
Just as a specific example, what if my application indeed needs ExceptionGroup's (and not just more flexible matching of existing exception chains), but I want it to be a bit simpler: a) to have a flat structure of contained exceptions; b) want to actually emphasize the users that they should not rely on the ordering, so want it to be a set.
My understanding is that ExceptionGroup is carefully designed to allow subclassing it and override behavior, and the above should be possible by subclassing.
Indeed: class PaulsExceptionGroup(ExceptionGroup): def __new__(cls, message, excs): for e in excs: if isinstance(e, BaseExceptionGroup): raise TypeError("me is a flat exception group") return super().__new__(cls, message, excs) @property def exceptions(self): return set(super().exceptions) def derive(self, excs): return PaulsExceptionGroup(self.message, excs) eg1 = PaulsExceptionGroup("Paul's group", [ValueError(12), TypeError(42)]) print(repr(eg1)) print(eg1.exceptions) match, rest = eg1.split(ValueError) print(f'{match=!r}\n{rest=!r}') print(f'{match.exceptions=}\n{rest.exceptions=}') Output: PaulsExceptionGroup("Paul's group", [ValueError(12), TypeError(42)]) {TypeError(42), ValueError(12)} match=PaulsExceptionGroup("Paul's group", [ValueError(12)]) rest=PaulsExceptionGroup("Paul's group", [TypeError(42)]) match.exceptions={ValueError(12)} rest.exceptions={TypeError(42)} See - split() just works for you out of the box. But usually, a simpler base class is subclassed to have
more advanced/complex behavior. So again, current ExceptionGroup feels kind of specialized with its careful attention to hierarchy and multi-story "3D" tracebacks.
So your concern with our design is that ExceptionGroup implements a generic split() that handles tracebacks and nested structure correctly, and you might not need that because maybe you don't nest or you don't care about tracebacks? If that is the case then I think you are confusing "generic" with "special-purpose". ExceptionGroup is generic, it works for general nested groups with arbitrary tracebacks and cause/context. That's the opposite of special-purpose. Irit