Remove traceback in Python on Ctrl-C
Asked Answered
A

9

35

Is there a way to keep tracebacks from coming up when you hit Ctrl+c, i.e. raise KeyboardInterrupt in a Python script?

Automotive answered 16/8, 2011 at 3:16 Comment(0)
L
34
import sys
try:
    # your code
except KeyboardInterrupt:
    sys.exit(0) # or 1, or whatever

Is the simplest way, assuming you still want to exit when you get a Ctrl+c.

If you want to trap it without a try/except, you can use a recipe like this using the signal module, except it doesn't seem to work for me on Windows..

Lasso answered 16/8, 2011 at 3:20 Comment(5)
@A-B-B an exit code of 1 is expected if exiting the program with Ctrl-C isn't the standard, non-error way of exiting it. If this method of exiting represents success, then zero is the expected exit code. Just because it's maybe a bad idea to use Ctrl-C this way doesn't mean people don't (and I've used programs that do).Lasso
This seems to interfere with input, at least on my system. Try putting input("Press enter to continue") inside the try block. When you press enter, it just prints ^M to stdout. This seems relevant.Accession
@Accession I just tried it and it definitely works fine for me. Reading the other issue you linked, it doesn't seem to have anything to do with the code here. Are you saying if you try this with a bare input it works, but if you add the try it doesn't? I think something else must be different.Lasso
@Lasso yes, on my system that's the case. The try is what makes the difference. Running stty sane makes it work again. I don't know what this does though!Accession
@Spence Why is exit code 1 expected? Shouldn't it be exit code 130 (i.e. 128 + SIGINT)?Marniemaro
F
41

Try this:

import signal
import sys
signal.signal(signal.SIGINT, lambda x, y: sys.exit(0))

This way you don't need to wrap everything in an exception handler.

Filaria answered 1/8, 2012 at 15:32 Comment(7)
It is fundamentally wrong to exit with status 0 given that SIGINT is triggered. The returned exit status must ideally be preserved, as if the exception were not caught.Spence
Not entirely understanding lambda; can you explain the use of "x,y:" here instead of just "x:"?Diez
@Diez it's because of this, basically the second argument of signal.signal is a function 'handler' that takes two parameters. For clarity x and y could be renamed like this signal.signal(signal.SIGINT, lambda signal_number, current_stack_frame: sys.exit(0))Weissmann
I was trying to stop stack-traces when hitting Ctrl-C on /usr/bin/openstack, and this approach worked partially for me. Adding the try/except solution in a previous answer works most of the time, but I still get a stack-trace sometimes - maybe because /usr/bin/openstack is just a wrapper for a module?Hyades
Unfortunately sys.exit() raises SystemExit which produces its own backtrace. Try using os._exit() instead.Cyclonite
@Spence So what do you suggest to do?Brahman
@Brahman Consider the answer by wim, etc.Spence
L
34
import sys
try:
    # your code
except KeyboardInterrupt:
    sys.exit(0) # or 1, or whatever

Is the simplest way, assuming you still want to exit when you get a Ctrl+c.

If you want to trap it without a try/except, you can use a recipe like this using the signal module, except it doesn't seem to work for me on Windows..

Lasso answered 16/8, 2011 at 3:20 Comment(5)
@A-B-B an exit code of 1 is expected if exiting the program with Ctrl-C isn't the standard, non-error way of exiting it. If this method of exiting represents success, then zero is the expected exit code. Just because it's maybe a bad idea to use Ctrl-C this way doesn't mean people don't (and I've used programs that do).Lasso
This seems to interfere with input, at least on my system. Try putting input("Press enter to continue") inside the try block. When you press enter, it just prints ^M to stdout. This seems relevant.Accession
@Accession I just tried it and it definitely works fine for me. Reading the other issue you linked, it doesn't seem to have anything to do with the code here. Are you saying if you try this with a bare input it works, but if you add the try it doesn't? I think something else must be different.Lasso
@Lasso yes, on my system that's the case. The try is what makes the difference. Running stty sane makes it work again. I don't know what this does though!Accession
@Spence Why is exit code 1 expected? Shouldn't it be exit code 130 (i.e. 128 + SIGINT)?Marniemaro
P
8

Catch the KeyboardInterrupt:

try:
    # do something
except KeyboardInterrupt:
    pass
Payer answered 16/8, 2011 at 3:19 Comment(0)
M
7

According to Proper handling of SIGINT/SIGQUIT, the only way to exit correctly if you're catching a SIGINT is to subsequently kill yourself with a SIGINT signal (see the section titled "How to be a proper program"). It is incorrect to attempt to fake the proper exit code. For more info about that, check also Why is "Doing an exit 130 is not the same as dying of SIGINT"? over on Unix Stack Exchange.

It seems all the answers here which are exiting zero actually demonstrate programs that misbehave. It's preferable not to hide that you were interrupted, so that the caller (usually a shell) knows what to do.

In Python, the traceback printout on stderr comes from the default sys.excepthook behaviour. So, to suppress the traceback spam, I've opted to tackle the problem directly at the cause by replacing the except hook on keyboard interrupt, and then preserve the correct exit code by re-raising the original exception:

import sys, time

def _no_traceback_excepthook(exc_type, exc_val, traceback):
    pass

def main():
    try:
        # whatever your program does here...
        print("hello world..")
        time.sleep(42)
    except KeyboardInterrupt:
        # whatever cleanup code you need here...
        print("bye")
        if sys.excepthook is sys.__excepthook__:
            sys.excepthook = _no_traceback_excepthook
        raise

if __name__ == "__main__":
    main()

The result is like this:

$ python3 /tmp/example.py
hello world..
^Cbye

$ echo $?
130

If you don't have any cleanup actions to execute, and all you wanted to do is suppress printing the traceback on interrupt, it may be simpler just to install that by default:

import sys

def _no_traceback_excepthook(exc_type, exc_val, traceback):
    if isinstance(exc_val, KeyboardInterrupt):
        return
    sys.__excepthook__(exc_type, exc_val, traceback)

sys.excepthook = _no_traceback_excepthook
Marniemaro answered 21/11, 2022 at 22:47 Comment(0)
P
1

Catch it with a try/except block:

while True:
   try:
      print "This will go on forever"
   except KeyboardInterrupt:
      pass
Porcia answered 16/8, 2011 at 3:19 Comment(0)
M
1
try:
    your_stuff()
except KeyboardInterrupt:
    print("no traceback")
Middlebuster answered 16/8, 2011 at 3:19 Comment(0)
A
0

Also note that by default the interpreter exits with the status code 128 + the value of SIGINT on your platform (which is 2 on most systems).

    import sys, signal

    try:
        # code...
    except KeyboardInterrupt: # Suppress tracebacks on SIGINT
        sys.exit(128 + signal.SIGINT) # http://tldp.org/LDP/abs/html/exitcodes.html
Arkose answered 13/11, 2011 at 1:17 Comment(2)
This is incorrect. Exiting with signal SIGINT is different from a exiting with code 128 + SIGINT. They may show up as the same $? in your shell, but they are distinguishable in the underlying API and have different effects. For example, typically, a shell script will stop execution when a child exits with a signal and not when it exits with a code.Montiel
@Spence Later changed in bugs.python.org/issue1054041Marniemaro
W
-1

suppress exception using context manager:

from contextlib import suppress

def output_forever():
    while True:
        print('endless script output. Press ctrl + C to exit')


if __name__ == '__main__':
    with suppress(KeyboardInterrupt):
        output_forever()
Warenne answered 13/2, 2022 at 9:34 Comment(0)
B
-7
import sys
try:
    print("HELLO")
    english = input("Enter your main launguage: ")
    print("GOODBYE")
except KeyboardInterrupt:
    print("GET LOST")
Byelection answered 30/6, 2015 at 8:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.