Python Language Summit: PEP 654 – Exception Groups and Except
pyfound.blogspot.com
pyfound.blogspot.com
async with asyncio.TaskGroup() as g:
g.create_task(concurrent_task1())
g.create_task(concurrent_task1())
reminds me of the structural concurrency: async with trio.open_nursery() as nursery:
nursery.start_soon(myfunc)
nursery.start_soon(anotherfunc)
> nurseries ... rather a new control flow primitive that's just as fundamental as for loops or function callshttps://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
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.
It's 100% a win.
Am I wrong? Please someone tell me I'm wrong
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.
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.
Do not implement a multiplexing GOTO.
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.
try:
x = uncertain_op()
except ...:
x = 'fallback'
do_something(x)
is a common Python idiom; no initial definition required. Although this isn't true in every language. try { /* new scope */ }
catch (FooException &foo) { /* another scope */ }
Python is probably one of not that many languages where this is not the case.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.
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 ? "
- they cannot make you end up any place in the code, only up the stack.
- going up in the stack, it leads to the destruction of any frame in the way.
- the exception object carries metadata about the origin and propagation.
- calling code can subscribe to the event and act on it. Handling is decoupled from raising.
- calling code can interrupt (in multiple spots) or stop the propagation.
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.
try:
foobar
delegate handle_stuff:
delegate handler_factory(something):
except Foo:
...
else:
...
finally
...
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. for (i = 0, i < n; i++) { a[i] }
- they're simpler to reason about because they restrict the conceptual operational space.