96
votes

I'm writing a command line utility in Python which, since it is production code, ought to be able to shut down cleanly without dumping a bunch of stuff (error codes, stack traces, etc.) to the screen. This means I need to catch keyboard interrupts.

I've tried using both a try catch block like:

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print 'Interrupted'
        sys.exit(0)

and catching the signal itself (as in this post):

import signal
import sys

def sigint_handler(signal, frame):
    print 'Interrupted'
    sys.exit(0)
signal.signal(signal.SIGINT, sigint_handler)

Both methods seem to work quite well during normal operation. However, if the interrupt comes during cleanup code at the end of the application, Python seems to always print something to the screen. Catching the interrupt gives

^CInterrupted
Exception KeyboardInterrupt in <bound method MyClass.__del__ of <path.to.MyClass object at 0x802852b90>> ignored

whereas handling the signal gives either

^CInterrupted
Exception SystemExit: 0 in <Finalize object, dead> ignored

or

^CInterrupted
Exception SystemExit: 0 in <bound method MyClass.__del__ of <path.to.MyClass object at 0x802854a90>> ignored

Not only are these errors ugly, they're not very helpful (especially to an end user with no source code)!

The cleanup code for this application is fairly big, so there's a decent chance that this issue will be hit by real users. Is there any way to catch or block this output, or is it just something I'll have to deal with?

2
Why don't you replace sys.stdout/sys.stderr? Like sys.stderr = open(os.devnull, 'w')?. If you really don't care about the final output then this seems like the obvious solution. - Bakuriu
There is an os._exit but it looks like nasal demons to me. Where is your cleanup code, are you using atexit module for that? - wim
@Bakuriu: While redirecting stderr will quiet the output, it also squashes legitimate errors that the user can do something about, like file-not-found or host-unreachable. - Dan
@Dan It doesn't have to be /dev/null. You can write a custom file-like object that hides only messages with a given format. - Bakuriu
@Bakuriu: Still seems pretty hacky to me. I'll do that if I have to, but I feel like this is something that ought to be built into the language. - Dan

2 Answers

133
votes

Checkout this thread, it has some useful information about exiting and tracebacks.

If you are more interested in just killing the program, try something like this (this will take the legs out from under the cleanup code as well):

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print('Interrupted')
        try:
            sys.exit(0)
        except SystemExit:
            os._exit(0)
9
votes

You could ignore SIGINTs after shutdown starts by calling signal.signal(signal.SIGINT, signal.SIG_IGN) before you start your cleanup code.