On Wed, Mar 3, 2021 at 4:37 PM Paul Moore <p.f.moore@gmail.com> 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.

See below.
 

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)

to:

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.

Irit