108

Is there some way in Python to capture KeyboardInterrupt event without putting all the code inside a try-except statement?

I want to cleanly exit without trace if user presses Ctrl+C.

Benyamin Jafari
  • 15,536
  • 14
  • 81
  • 116
Alex
  • 1,804
  • 4
  • 22
  • 27

7 Answers7

157

Yes, you can install an interrupt handler using the module signal, and wait forever using a threading.Event:

import signal
import sys
import time
import threading

def signal_handler(signal, frame):
    print('You pressed Ctrl+C!')
    sys.exit(0)

signal.signal(signal.SIGINT, signal_handler)
print('Press Ctrl+C')
forever = threading.Event()
forever.wait()
Johan Kotlinski
  • 23,690
  • 9
  • 73
  • 100
  • 11
    Note that there are some platform-specific issues with the signal module -- shouldn't affect this poster, but "On Windows, signal() can only be called with SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, or SIGTERM. A ValueError will be raised in any other case." – bgporter Nov 17 '10 at 14:39
  • 7
    Works well with threads, too. I hope you don't ever do `while True: continue`, though. (In that style, `while True: pass` would be neater, anyway.) That'd be very wasteful; try something like `while True: time.sleep(60 * 60 * 24)` (sleeping for a day at a time is an entirely arbitrary figure). – Chris Morgan Oct 06 '11 at 12:04
  • 1
    If you're using Chris Morgan's suggestion of using `time` (as you should), don't forget to `import time` :) – Seaux May 30 '13 at 17:37
  • 1
    Calling sys.exit(0) triggers a SystemExit exception for me. You can make it work nicely if you use it in combination with this: http://stackoverflow.com/a/13723190/353094 – leetNightshade Mar 21 '15 at 19:42
  • 2
    You can use signal.pause() instead of sleeping repeatedly – Croad Langshan Jul 05 '15 at 13:49
  • This doesn't completely terminate a program if using multiprocessing – Jake Aug 15 '17 at 15:18
  • Also doesn't work for me... it seems like forever.wait() just blocks and the signal handler function is never called. Could this example be operating system specific? I am on Windows 10. – Chris Sep 09 '19 at 11:07
39

If all you want is to not show the traceback, make your code like this:

## all your app logic here
def main():
   ## whatever your app does.


if __name__ == "__main__":
   try:
      main()
   except KeyboardInterrupt:
      # do nothing here
      pass

(Yes, I know that this doesn't directly answer the question, but it's not really clear why needing a try/except block is objectionable -- maybe this makes it less annoying to the OP)

bgporter
  • 30,774
  • 8
  • 54
  • 62
  • 5
    For some reason, this doesn't always work for me. `signal.signal( signal.SIGINT, lambda s, f : sys.exit(0))` always does. – Hal Canary Jul 13 '13 at 18:15
  • This doesn't always work with things such as pygtk which use threads. Sometimes ^C will just kill the current thread instead of the entire process, so the exception will only propagate through that thread. – Sudo Bash Oct 23 '13 at 01:56
  • There's another SO question specifically about Ctrl+C with pygtk: http://stackoverflow.com/questions/16410852/keyboard-interrupt-with-with-python-gtk – bgporter Oct 23 '13 at 13:46
31

An alternative to setting your own signal handler is to use a context-manager to catch the exception and ignore it:

>>> class CleanExit(object):
...     def __enter__(self):
...             return self
...     def __exit__(self, exc_type, exc_value, exc_tb):
...             if exc_type is KeyboardInterrupt:
...                     return True
...             return exc_type is None
... 
>>> with CleanExit():
...     input()    #just to test it
... 
>>>

This removes the try-except block while preserving some explicit mention of what is going on.

This also allows you to ignore the interrupt only in some portions of your code without having to set and reset again the signal handlers everytime.

Bakuriu
  • 85,459
  • 18
  • 168
  • 202
  • 1
    nice, this solution does seem a bit more direct in expressing the purpose rather than dealing with signals. – Seaux May 30 '13 at 17:50
  • Using multiprocessing library, I'm not sure on which object I should add those methods .. any clue ? – Stéphane Mar 20 '19 at 11:37
  • @Stéphane What do you mean? When dealing with multiprocessing you will have to deal with the signal in both the parent and child processes, since it might be triggered in both. It really depends on what you are doing and how your software will be used. – Bakuriu Mar 20 '19 at 21:21
8

I know this is an old question but I came here first and then discovered the atexit module. I do not know about its cross-platform track record or a full list of caveats yet, but so far it is exactly what I was looking for in trying to handle post-KeyboardInterrupt cleanup on Linux. Just wanted to throw in another way of approaching the problem.

I want to do post-exit clean-up in the context of Fabric operations, so wrapping everything in try/except wasn't an option for me either. I feel like atexit may be a good fit in such a situation, where your code is not at the top level of control flow.

atexit is very capable and readable out of the box, for example:

import atexit

def goodbye():
    print "You are now leaving the Python sector."

atexit.register(goodbye)

You can also use it as a decorator (as of 2.6; this example is from the docs):

import atexit

@atexit.register
def goodbye():
    print "You are now leaving the Python sector."

If you wanted to make it specific to KeyboardInterrupt only, another person's answer to this question is probably better.

But note that the atexit module is only ~70 lines of code and it would not be hard to create a similar version that treats exceptions differently, for example passing the exceptions as arguments to the callback functions. (The limitation of atexit that would warrant a modified version: currently I can't conceive of a way for the exit-callback-functions to know about the exceptions; the atexit handler catches the exception, calls your callback(s), then re-raises that exception. But you could do this differently.)

For more info see:

driftcatcher
  • 1,981
  • 4
  • 26
  • 44
4

You can prevent printing a stack trace for KeyboardInterrupt, without try: ... except KeyboardInterrupt: pass (the most obvious and propably "best" solution, but you already know it and asked for something else) by replacing sys.excepthook. Something like

def custom_excepthook(type, value, traceback):
    if type is KeyboardInterrupt:
        return # do nothing
    else:
        sys.__excepthook__(type, value, traceback)
Richard
  • 44,865
  • 24
  • 144
  • 216
  • I want clean exit without trace if user press ctrl-c – Alex Nov 17 '10 at 14:29
  • 7
    This is not true at all. The KeyboardInterrupt exception is created during an interrupt handler. The default handler for SIGINT raises the KeyboardInterrupt so if you didn't want that behavior all you would have to do is provide a different signal handler for SIGINT. Your are correct in that exceptions can only be handled in a try/except however in this case you can keep the exception from ever being raised in the first place. – Matt Dec 20 '12 at 14:17
  • 1
    Yeah, I learned that about three minutes after posting, when kotlinski's answer rolled in ;) –  Dec 20 '12 at 17:31
2

I tried the suggested solutions by everyone, but I had to improvise code myself to actually make it work. Following is my improvised code:

import signal
import sys
import time

def signal_handler(signal, frame):
    print('You pressed Ctrl+C!')
    print(signal) # Value is 2 for CTRL + C
    print(frame) # Where your execution of program is at moment - the Line Number
    sys.exit(0)

#Assign Handler Function
signal.signal(signal.SIGINT, signal_handler)

# Simple Time Loop of 5 Seconds
secondsCount = 5
print('Press Ctrl+C in next '+str(secondsCount))
timeLoopRun = True 
while timeLoopRun:  
    time.sleep(1)
    if secondsCount < 1:
        timeLoopRun = False
    print('Closing in '+ str(secondsCount)+ ' seconds')
    secondsCount = secondsCount - 1
Rohit Jain
  • 21
  • 1
0

If someone is in search for a quick minimal solution,

import signal

# The code which crashes program on interruption

signal.signal(signal.SIGINT, call_this_function_if_interrupted)

# The code skipped if interrupted
tejasvi88
  • 352
  • 3
  • 11