On Wed, Mar 3, 2021 at 4:37 PM Paul Moore firstname.lastname@example.org wrote:
Similar to the argument for "except Exception". Applications that trap KeyboardInterrupt so that they can exit cleanly without an ugly traceback will no longer trap *all* keyboard interrupts, as they could miss grouped ones.
If we accept that grouped exceptions should never escape out of a well-defined context, then this wouldn't be such an issue. But there's nothing in the PEP that enforces that, and there *is* code that needs to be prepared for "any sort of result". It's the except Exception argument again.
So code that wants to exit cleanly in the face of Ctrl-C will need to be rewritten from:
try: main() except KeyboardInterrupt: print("User interrupted the program. Exiting") sys.exit(1)
try: try: main() except KeyboardInterrupt: print("User interrupted the program. Exiting") sys.exit(1) except *KeyboardInterrupt: print("User interrupted the program. Exiting") sys.exit(1)
It suffices to do
try: main() except *KeyboardInterrupt: print("User interrupted the program. Exiting") sys.exit(1)
because "except *T" catches Ts as well.
Did I miss an easier way of writing this code? And worse, how would I write it so that it was portable between Python 3.9 and later versions (which is a common requirement for library code - admittedly library code wouldn't normally be doing this sort of top-level trap, but it could just as easily be "catch Ctrl-C and do a bit of tidy-up and re-raise").
For older Pythons you would have to do something like
except KeyboardInterrupt: ... except BaseExceptionGroup: # some stub type in old versions # inspect the contents # if there's a KeyboardInterrupt do what you need to do # reraise the rest
I think the only reason you're comfortable with having to select between
the exceptions that were raised and discard some of them is because that's where we are today. The PEP lists several standard library and other APIs that discard errors because they need to pick one. That's what we're trying to fix.
Maybe. But I'm not looking at it as being "comfortable" with the current situation, but rather as "I don't use any of these new features, why am I having to change my code to accommodate stuff I don't use?" If I own the full stack, that's not an issue, but frameworks and libraries typically have to interact with other users' code, and there the contract has changed from "do what you like in your code and I'll cope" to "do what you like in your code as long as you don't let an exception group escape, and I'll cope"... And I have to change *my* code to get the old contract back.
"except Exception"/"except BaseException" is the special case of "I don't know what I'm calling and I want to catch everything". And that (to repeat, just in case) will work as you expect.
If you are actually handling exceptions selectively, then I can break you already in 3.9 just by raising a different exception type to the one you are catching. How is this different?
Raising an ExceptionGroup is an API change. If you call APIs that say they will raise ExceptionGroups you need to update your code accordingly. If a library doesn't document that it raises ExceptionGroups and then one of those escapes, then that library has a bug. Just like with any other exception type.
But it's a small point in the wider scheme of things, and I'm not going to labour the point any more. Thanks for listening and taking the time to reply.
It's an important one though. Thanks for asking good questions.