33

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

Mwiza
  • 7,780
  • 3
  • 46
  • 42
Kyle Hotchkiss
  • 10,754
  • 20
  • 56
  • 82

9 Answers9

43

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.

scrutari
  • 1,378
  • 2
  • 17
  • 33
jleahy
  • 16,149
  • 6
  • 47
  • 66
  • 9
    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. – Asclepius Jun 24 '17 at 15:20
  • 1
    Not entirely understanding lambda; can you explain the use of "x,y:" here instead of just "x:"? – Locane Jul 28 '17 at 06:25
  • @Locane it's because of [this](https://docs.python.org/3.7/library/signal.html#signal.signal), 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))` – oidualc Aug 08 '17 at 10:47
  • 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? – Laurence Renshaw May 26 '21 at 09:53
  • Unfortunately `sys.exit()` raises `SystemExit` which produces its own backtrace. Try using `os._exit()` instead. – kbro Feb 16 '22 at 09:56
  • @Asclepius So what do you suggest to do? – Tropilio Feb 03 '23 at 11:12
  • @Tropilio Consider the [answer by wim](https://stackoverflow.com/a/74525682/), etc. – Asclepius Feb 03 '23 at 19:06
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..

Mwiza
  • 7,780
  • 3
  • 46
  • 42
agf
  • 171,228
  • 44
  • 289
  • 238
  • 4
    @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). – agf Jun 24 '17 at 22:08
  • 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](https://askubuntu.com/a/452576/269136) seems relevant. – LondonRob Dec 15 '20 at 10:19
  • @LondonRob 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. – agf Dec 16 '20 at 22:22
  • @agf 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! – LondonRob Dec 17 '20 at 11:09
  • 1
    @Asclepius Why is exit code 1 expected? Shouldn't it be exit code 130 (i.e. 128 + SIGINT)? – wim Nov 21 '22 at 22:48
9

Catch the KeyboardInterrupt:

try:
    # do something
except KeyboardInterrupt:
    pass
icktoofay
  • 126,289
  • 21
  • 250
  • 231
4

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
wim
  • 338,267
  • 99
  • 616
  • 750
2

Catch it with a try/except block:

while True:
   try:
      print "This will go on forever"
   except KeyboardInterrupt:
      pass
Manny D
  • 20,310
  • 2
  • 29
  • 31
2
try:
    your_stuff()
except KeyboardInterrupt:
    print("no traceback")
ʇsәɹoɈ
  • 22,757
  • 7
  • 55
  • 61
1

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
toksaitov
  • 171
  • 1
  • 4
  • 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. – Anders Kaseorg Oct 05 '20 at 22:10
  • @Asclepius Later changed in https://bugs.python.org/issue1054041 – wim Nov 21 '22 at 22:50
0

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()
Roman Kazakov
  • 697
  • 7
  • 12
-6
import sys
try:
    print("HELLO")
    english = input("Enter your main launguage: ")
    print("GOODBYE")
except KeyboardInterrupt:
    print("GET LOST")
fuesika
  • 3,280
  • 6
  • 26
  • 34