Hi Paul,
IIUC, you are saying that exception group should not be a builtin type because it is (1) complex (2) special-purposed. Instead, you propose that we make exception handling pluggable.
(1) I agree, it is somewhat complex - it took us several iterations to get from the idea of "a container of exceptions" to something that works correctly with split()/subgroup(), maintaining the integrity of __traceback__/__cause__/__context__ information, allowing subclassing and having a relatively simple API. To me this is a reason to provide it as a builtin rather than a reason not to (I think we should add this point to the PEP- thanks).
You wrote:
> So yes, maybe being well aware that you're handling exactly Trio's (or asyncio's, or context manager's) error isn't bad thing actually.
Maybe, in which case you can create a subclass of ExceptionGroup to mark the groups coming from your library. But also maybe not, in which case you don't have to.
(2) special-purposed? How so? When I asked for a practical advantage of your pluggable solution I meant an example of a use case that our proposal doesn't accommodate well and where a pluggable one does better.
The only example you suggested so far was the TemporaryDirectory one, which I don't find compelling:
> > 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? Even without that problem, why should I prefer this to our proposal?
I am interested to know (1) what limitations you see in the ExceptionGroup type we are proposing (in what sense is it special-purposed? what purpose is it unsuitable for?) and (2) an example where the pluggable solution works better.
Irit
Hello,
On Sat, 27 Mar 2021 10:55:40 +0000
Irit Katriel <iritkatriel@googlemail.com> wrote:
> 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.
Looking from a different angle shows a different perspective:
1. Trio devised an interesting concept of "nurseries" to deal with
multiple tasks in asyncio-like programming.
2. It was all nice and beautiful until ... it came to error handling.
Note that it's a rather typical situation - one can write nice, clean,
beautiful code, which is not adequate in real-world scenarios,
particularly because of error handling concerns. Oftentimes, such
initial code/concepts are discarded, and more robust (though maybe not
as beautiful) concepts/code is used.
3. But that's not what happened in the Trio case. The concept of
nurseries got pushed forward, until it became clear that it requires
changes on the programming language level.
4. That's how PEP654 was born, which, besides async scheduling of
multiple tasks, bring *somewhat similar* usecases of e.g. raising
exceptions thru context managers' own exceptions.
Note that where errors and exceptions lead us is in questions on how to
handle them. And beyond a couple of well known patterns ("dump and
crash" or "dump and continue with next iteration"), error handling is
very adhoc to a particular application and particular error(s). Seen
from that angle, Trio wants to vendor-lock the entire language into its
particular (experimental) way of handling multiple errors.
So yes, maybe being well aware that you're handling exactly Trio's (or
asyncio's, or context manager's) error isn't bad thing actually. And if
it's clear that multiple asyncio frameworks are interested in sharing
common exception base class for such usecases, then it could be
introduced on the "asyncio" package level, or maybe even better, as a
module similar to "concurrent.futures".
> 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?
The concern is that it codifies pretty complex and special-purpose
things on the language level. And it seems that the whole concept is
rather experimental and "original design". We can compare that with
another sufficiently complex feature which landed recently: pattern
matching. At all phases of the design and discussion of that feature,
one of the guiding principles was: "Many other languages implement it,
so we know it's generally useful, and have design space more or less
charted, and can vary things to find local optimum for Python".
Contrary to that, the original PEP654 didn't refer to (cross-language,
cross-framework) prior art in handling multiple errors, and I don't see
that changed in the latest version. So I'm sorry, but it seems like NIH
feature of a specific 3rd-party framework being promoted to a whole
language's way of doing things.
Under such circumstance, I guess it would be good idea to try to
decouple behavior of that feature from the languages core, and make
aspects of behavior more explicit (following "explicit is better than
implicit" principle), and allow to vary/evolve it without changing the
core language.
I tried to draft a scheme aspiring to allow that. (Which would
definitely need more work to achieve parity with functionality in
PEP654, because again, it tries to codify rather complex and "magic"
behavior. Where complex and magic behavior in exception handling is
itself of concern, so making it more explicit may be a good idea. (A
good "how other languages deal with it") review would mention that Go,
Rust, Zig, etc. don't have and condemn exception handling at all (normal
simple classy exceptions, not magic we discuss here!)).
Thanks,
Paul