Reputation: 2074
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.
Upvotes: 124
Views: 164370
Reputation: 1332
If what you want is only to exit without trace but you don’t need to catch the KeyboardInterrupt signal, then you can use sys.excepthook
to silent the traceback message and inside it you can use ANSI escape codes to remove the ^C
characters.
"\r" Go to the beginning of line
"\033[2K" Erase line
"\033[A" Go one line up
import sys
def main():
input()
def gracefull_KeyboardInterrupt():
sys.excepthook = lambda type, value, traceback: (
print("\r" "\033[2K" "\033[A", end="", flush=True)
if issubclass(type, KeyboardInterrupt)
else None
)
if __name__ == "__main__":
gracefull_KeyboardInterrupt()
main()
Upvotes: 1
Reputation: 41
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
Upvotes: 4
Reputation: 25729
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()
Upvotes: 174
Reputation: 101919
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.
Upvotes: 33
Reputation:
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)
Upvotes: 4
Reputation: 2270
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:
atexit
Upvotes: 10
Reputation: 36474
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)
Upvotes: 48