So this is replacing one cumbersome and unintuitive syntax (MultiError.catch) with a slightly shorter syntax (try except*). Given the amount of verbiage in the PEP and this blog post I'm guessing it will still be unintuitive though.
Not just slightly shorter, because you need a handler for with MultiError.catch, which will be a function with and if and an instance check.
Also, it doesn't give you easy "finally" and "else" clauses.
And "except *" calls the same handler for single and multi-errors, so for most cases, you avoid code duplication.
Plus, having a natural way to deal with this make the code easier to scan, not to forget IDE already have good support for a try/except, and wrapping things in it.
The main advantage of this PEP over MultiError (IMO) is that standard except blocks will Just Work. For most use cases that will be sufficient, and those people will never have to think about this PEP.
This is exactly the type of thing that should be in PEPs right now.
It's practical, its forward thinking addressing an existing problem that's really prevalent in the py3 native async features and it's not incremental it addresses the problem in its totality.
I’m a bit of a pythonista, but I haven’t worked on anything very complex for a couple of years. Maybe my experience is still a little shallow in SWE topics, but can you reify this a bit? What kind of situations have you run into where handling multiple exceptions would have been handy? Maybe it’s telling that I’d have to admit that I’m not sure how big a difference there is between error code returns and exception handling.
It's just that the diagram shown in the article/blog were more inspiring than 99% what I heard about error abstractions on any language (and yeah it's not about python only). I just wished to read more about very general solutions on nested/stacked calls with exceptions and how to reduce the perimeter and make things more readable (honestly most of the time exception handling for me was logger.error or wrap/rethrow.. nothing exciting)
This PEP is a footgun. Remember that exceptions are GOTO.
If your exception-handling code is complex enough to be at all tricky you have a state machine and it behooves you to make that ad hoc machine explicit.
I see this sentiment a lot in language discussions; I really think the exceptions==goto parallel is overstated.
Exceptions are a recognition of and abstraction over a common pattern from exception-less languages: "if <error state>, return early with a special value and some info on the error". Client functions follow a pattern of "if <error returned>, then handle the error or propogate it upwards".
If you factor out that chain of early returns, you get "throw"/"raise". If you factor out the error handling, you get "catch"/"except". And if you turn that error info into an object/ADT, then you get the "Exception" class.
Exceptions are not adding anything new; it's syntactic sugar over a more verbose pattern you'd probably be using anyway. You could totally implement a clunky but logically equivalent version of exceptions yourself in any language.
Now, that being said, there's definitely a case to be made for avoiding over-abstracting and preferring explicitness to implicit magic. But that's much different than saying that exceptions are a under-abstraction like GOTO.
I routinely encounter places where I am forced to use try..except where it would be better and more easily understood if I did not have to work around the fact I am forced to goto out of the current scope.
I didn't downvote you, but I suspect you have been because you're mistaken. try...except doesn't force you to go out of scope, it keeps you in it. raising an exception leaves the scope, but nobody forces you to use it.
Not mistaken. The try..except creates a scope. It is not always, but often, that I need to address this new nested scope when structuring exception handling when I could simply not if exceptions were not used.
This is not true and I wish people would stop repeating this falsehood. If you had multi-valued returns and propagated every single error at every call site you will have implemented exceptions exactly.
Many excellent points. I had never encountered the idea of exception=goto. My initial thoughts:
1) In the case of goto, behavior is determined in the invocation. Conversely, when throwing an exception, the behavior is determined by the surrounding try block, which very is often well outside the scope of that throw. In this sense they seem like polar opposites.
2) You can model exceptions as monads: create a wrapper type that can either hold a regular value or a failure. Then create a helper function that processes these wrapped values. When encountering a wrapped regular value, the helper unwraps it and runs the next computation; on encountering a failure the helper skips the next computation and passes on the failure. The computations take unwrapped regular values as input, but they return the wrapped type. Not only is this how exceptions are done in pure functional languages, to my mind it’s the most implementation-independent way to think about what they actually are. This description bears no resemblance to goto.
> If your exception-handling code is complex enough to be at all tricky you have a state machine and it behooves you to make that ad hoc machine explicit.
1980: "that error handling code is a tedious and repetitive state machine, how could we abstract it ? ... let's create exceptions !"
2020: "exceptions implement a state machine, that state machine should be visible error-handling code ! let's remove exceptions and make error handling explicit"
2060: "that error handling code is a tedious and repetitive state machine, how could we abstract it ? "
Where "delegate handle_stuff" means that for any exception handle_stuff is called with the exception tuple and the exception is considered handled if it returns truthy. "delegate" of course takes an expression so that you can have Proper Adult Fun Handling Exceptions.
The "delegate" statement encapsulates which exceptions are handled and how in the supplied callable expression. This is functionally different from repeating "except (FooExc, BarExc, BazExc) as exc: handle(exc)". It would also save two to three lines on average.
https://vorpus.org/blog/notes-on-structured-concurrency-or-g...
> Implementing a better task spawning API in asyncio, inspired by Trio nurseries, was the main motivation for this PEP https://www.python.org/dev/peps/pep-0654/#motivation