727

I'm working on a python script that starts several processes and database connections. Every now and then I want to kill the script with a Ctrl+C signal, and I'd like to do some cleanup.

In Perl I'd do this:

$SIG{'INT'} = 'exit_gracefully';

sub exit_gracefully {
    print "Caught ^C \n";
    exit (0);
}

How do I do the analogue of this in Python?

0

13 Answers 13

1078

Register your handler with signal.signal like this:

#!/usr/bin/env python
import signal
import sys

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

signal.signal(signal.SIGINT, signal_handler)
print('Press Ctrl+C')
signal.pause()

Code adapted from here.

More documentation on signal can be found here.  

16
  • 25
    Could you tell me why to use this in stead of a KeyboardInterrupt exception? Isn't that more intuitive to use?
    – noio
    Commented Jun 10, 2011 at 14:07
  • 61
    Noio: 2 reasons. First, SIGINT can be sent to your process any number of ways (e.g., 'kill -s INT <pid>'); I'm not sure if KeyboardInterruptException is implemented as a SIGINT handler or if it really only catches Ctrl+C presses, but either way, using a signal handler makes your intent explicit (at least, if your intent is the same as OP's). More importantly though, with a signal you don't have to wrap try-catches around everything to make them work, which can be more or less of a composability and general software engineering win depending on the structure of your application.
    – Matt J
    Commented Jun 20, 2011 at 7:55
  • 58
    Example of why you want to trap the signal instead of catch the Exception. Say you run your program and redirect the output to a log file, ./program.py > output.log. When you press Ctrl-C you want your program to exit gracefully by having it log that all data files have been flushed and marked clean to confirm they are left in a known good state. But Ctrl-C sends SIGINT to all processes in a pipeline, so the shell may close STDOUT (now "output.log") before program.py finishes printing the final log. Python will complain, "close failed in file object destructor: Error in sys.excepthook:". Commented Jul 3, 2011 at 22:07
  • 27
    Note that signal.pause() is unavailable on Windows. docs.python.org/dev/library/signal.html
    – May Oakes
    Commented Aug 3, 2011 at 16:30
  • 19
    -1 unicorns for using signal.pause(), suggests that I would have to wait at such a blocking call instead of doing some real work. ;)
    – Nick T
    Commented Aug 3, 2014 at 15:49
219

You can treat it like an exception (KeyboardInterrupt), like any other. Make a new file and run it from your shell with the following contents to see what I mean:

import time, sys

x = 1
while True:
    try:
        print x
        time.sleep(.3)
        x += 1
    except KeyboardInterrupt:
        print "Bye"
        sys.exit()
3
  • 37
    Attention when using this solution. You should use also this code before KeyboardInterrupt catch block: signal.signal(signal.SIGINT, signal.default_int_handler), or you're going to fail, because KeyboardInterrupt does not fire in every situation in which it should fire! Details are here.
    – Velda
    Commented Nov 24, 2016 at 15:23
  • this wont work for strg+d? even with the signal codeline before
    – Asara
    Commented Jul 5, 2020 at 11:50
  • The problem with KeyboardInterrupt is, it'll move you out of the try block. It may not be graceful all the time. Think about long running loop. signal.signal gives you more control. Commented Jun 16, 2022 at 9:08
83

And as a context manager:

import signal

class GracefulInterruptHandler(object):

    def __init__(self, sig=signal.SIGINT):
        self.sig = sig

    def __enter__(self):

        self.interrupted = False
        self.released = False

        self.original_handler = signal.getsignal(self.sig)

        def handler(signum, frame):
            self.release()
            self.interrupted = True

        signal.signal(self.sig, handler)

        return self

    def __exit__(self, type, value, tb):
        self.release()

    def release(self):

        if self.released:
            return False

        signal.signal(self.sig, self.original_handler)

        self.released = True

        return True

To use:

with GracefulInterruptHandler() as h:
    for i in xrange(1000):
        print "..."
        time.sleep(1)
        if h.interrupted:
            print "interrupted!"
            time.sleep(2)
            break

Nested handlers:

with GracefulInterruptHandler() as h1:
    while True:
        print "(1)..."
        time.sleep(1)
        with GracefulInterruptHandler() as h2:
            while True:
                print "\t(2)..."
                time.sleep(1)
                if h2.interrupted:
                    print "\t(2) interrupted!"
                    time.sleep(2)
                    break
        if h1.interrupted:
            print "(1) interrupted!"
            time.sleep(2)
            break

From here: https://gist.github.com/2907502

2
  • It could also throw a StopIteration to break the innermost loop when a ctrl-C is pressed, right? Commented Feb 5, 2014 at 1:28
  • @TheoBelaire Instead of just throwing a StopIteration, I would create a generator that accepts an iterable as a parameter and registers/releases the signal handler.
    – Udi
    Commented Sep 18, 2014 at 8:27
33

You can handle CTRL+C by catching the KeyboardInterrupt exception. You can implement any clean-up code in the exception handler.

33

Yet Another Snippet

Referred main as the main function and exit_gracefully as the Ctrl+C handler

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        pass
    finally:
        exit_gracefully()
4
  • 8
    You should only use except for stuff that isn't supposed to happen. In this case KeyboardInterrupt is supposed to happen. So this is not a good construction.
    – Tristan
    Commented Jul 29, 2016 at 15:20
  • 28
    @TristanT In any other language yes, but in Python exceptions are not just for things that are not supposed to happen. It is actually considered good style in Python to use exceptions for flow control (where appropriate).
    – Ian Goldby
    Commented Nov 29, 2017 at 13:57
  • 2
    why not just exit_gracefully() in except instead of passing and adding finally?
    – Break
    Commented Jan 27, 2021 at 4:24
  • 6
    @Break its in finally so exit_gracefully() will also be called after main() finish Commented Jan 27, 2021 at 8:39
30

From Python's documentation:

import signal
import time

def handler(signum, frame):
    print 'Here you go'

signal.signal(signal.SIGINT, handler)

time.sleep(10) # Press Ctrl+c here
15

If you want to ensure that your cleanup process finishes I would add on to Matt J's answer by using a SIG_IGN so that further SIGINT are ignored which will prevent your cleanup from being interrupted.

import signal
import sys

def signal_handler(signum, frame):
    signal.signal(signum, signal.SIG_IGN) # ignore additional signals
    cleanup() # give your process a chance to clean up
    sys.exit(0)

signal.signal(signal.SIGINT, signal_handler) # register the signal with the signal handler first
do_stuff()
1
  • 2
    Thx. This is the sensible way of doing it, by clearly registering the signal handler.
    – not2qubit
    Commented Sep 27, 2023 at 18:32
12

I adapted the code from @udi to support multiple signals (nothing fancy) :

class GracefulInterruptHandler(object):
    def __init__(self, signals=(signal.SIGINT, signal.SIGTERM)):
        self.signals = signals
        self.original_handlers = {}

    def __enter__(self):
        self.interrupted = False
        self.released = False

        for sig in self.signals:
            self.original_handlers[sig] = signal.getsignal(sig)
            signal.signal(sig, self.handler)

        return self

    def handler(self, signum, frame):
        self.release()
        self.interrupted = True

    def __exit__(self, type, value, tb):
        self.release()

    def release(self):
        if self.released:
            return False

        for sig in self.signals:
            signal.signal(sig, self.original_handlers[sig])

        self.released = True
        return True

This code support the keyboard interrupt call (SIGINT) and the SIGTERM (kill <process>)

2
  • Did you try this with SIGTERM? It appears that the original handler for SIGTERM is not callable.
    – 1' OR 1 --
    Commented Sep 19, 2021 at 23:18
  • I've added a print comment in the handler, and got the event for both SIGINT and SIGTERM. Adding a print to the condition (if h.interupted:) was shown too. So I think it works yes.
    – Cyril N.
    Commented Sep 21, 2021 at 6:16
10

In contrast to Matt J his answer, I use a simple object. This gives me the possibily to parse this handler to all the threads that needs to be stopped securlery.

class SIGINT_handler():
    def __init__(self):
        self.SIGINT = False

    def signal_handler(self, signal, frame):
        print('You pressed Ctrl+C!')
        self.SIGINT = True


handler = SIGINT_handler()
signal.signal(signal.SIGINT, handler.signal_handler)

Elsewhere

while True:
    # task
    if handler.SIGINT:
        break
4
  • You should use an event or time.sleep() instead of doing a busy loop on a variable.
    – OlivierM
    Commented May 30, 2019 at 7:43
  • 1
    @OlivierM This is really application specific and definitely not the point of this example. For example, blocking calls or await functions won't keep the CPU busy. Furthermore, this is just an example of how things can be done. KeyboardInterrupts are often enough, like mentioned in other answers. Commented May 31, 2019 at 13:28
  • @ThomasDevoogdt I love the elegance of this solution -- I can completely understand what's going on here. Can you comment on why this is superior (or inferior) to the other solutions? In particular, I find Udi/Cyril N.'s solutions to be very complete, but much more complex. Commented Oct 23, 2020 at 19:58
  • I don't like to call this solution superior over another. A good developer looks at different answers and distills its own application-specific solution out of the available suggestions. But to give one advantage, it's pretty simple to use and implement. A rather big disadvantage is the need for a non-blocking loop in the thread that is handled. Commented Oct 25, 2020 at 13:40
4

You can use the functions in Python's built-in signal module to set up signal handlers in python. Specifically the signal.signal(signalnum, handler) function is used to register the handler function for signal signalnum.

3

thanks for existing answers, but added signal.getsignal()

import signal

# store default handler of signal.SIGINT
default_handler = signal.getsignal(signal.SIGINT)
catch_count = 0

def handler(signum, frame):
    global default_handler, catch_count
    catch_count += 1
    print ('wait:', catch_count)
    if catch_count > 3:
        # recover handler for signal.SIGINT
        signal.signal(signal.SIGINT, default_handler)
        print('expecting KeyboardInterrupt')

signal.signal(signal.SIGINT, handler)
print('Press Ctrl+c here')

while True:
    pass
1
  • An alternative to signal.getsignal is to save the return value of signal.signal, which is the same thing. Commented Apr 5, 2021 at 8:01
1

Here's a one-liner (minus the imports), tested with Python 3.10 (not sure about other versions):

#!/usr/bin/env python3
from signal import SIGINT, SIGTERM, sigwait


print('received', sigwait({SIGINT, SIGTERM}))

Though the behavior is funny if you send another signal, like USR1, before INT/TERM. sigwait will wait for INT/TERM, but won't output the "received {signal}" line as expected.

$ ./test.py # ctrl + c
^Creceived Signals.SIGINT

$ ./test.py # kill -TERM <pid>
received Signals.SIGTERM

$ ./test.py # kill -USR1 <pid> ; kill -TERM <pid>
User defined signal 1: 30
2
0

Personally, I couldn't use try/except KeyboardInterrupt because I was using standard socket (IPC) mode which is blocking. So the SIGINT was cueued, but came only after receiving data on the socket.

Setting a signal handler behaves the same.

On the other hand, this only works for an actual terminal. Other starting environments might not accept Ctrl+C, or pre-handle the signal.

Also, there are "Exceptions" and "BaseExceptions" in Python, which differ in the sense that interpreter needs to exit cleanly itself, so some exceptions have a higher priority than others (Exceptions is derived from BaseException)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.