How to hangle signals in python?

Signal are a means by which you can communicate with a process (or processes can communicate between each others). The two most popular signals are SIGINT and SIGTSTP.

You can send a SIGINT signal to the process currently running in your terminal with ctrl+c or cmd+c. This signal should cause the process to terminate immediately.

You can send a SIGTSTP signal with ctrl+z or cmd+z. This signal will pause the process and give you the prompt back. You can later use the fg command to send a CONT signal and resume the process.

By default, python will throw a KeyboardInterrupt when it receives SIGINT and will stop executing your script when it receives SIGTSTP.

The signal module allows to register custom handlers to be executed when a signal is received. If you hold resources that need to be released (e.g. network connections), you can do so through these handlers.

For instance, If your script is a scraper responsible for downloading data from a password-protected website, you will likely want to setup a handler on the CONT signal to make sure that the connection is established and the session restaured when the script is resumed.

Here's how to register handlers for these three signals (also on github gist):

import signal, time, sys, os
import logging
from logging import info

def sigint_handler(signum, frame):
    info(f'sigint_handler: Received signal {signum} on frame {frame}')

def sigtstp_handler(signum, frame):
    info(f'sigtstp_hangler: Received signal {signum} on frame {frame}')
    # ... release resource here ...
    # Remove our handler
    signal.signal(signum, signal.SIG_DFL)
    # Resend signal to pause process
    os.kill(os.getpid(), signum)
    # Back from suspend -- reestablish the handler.
    signal.signal(signal.SIGTSTP, sigtstp_handler)

def sigcont_handler(signum, frame):
    info(f'sigcont_handler: Received signal {signum} on frame {frame}')
    time.sleep(0.5) # acquire resource here ...
    info('Ready to go')

logging.basicConfig(level=logging.INFO, format='\n%(message)s')
# Assign handlers to signals
signal.signal(signal.SIGINT, sigint_handler)
signal.signal(signal.SIGTSTP, sigtstp_handler)
signal.signal(signal.SIGCONT, sigcont_handler)

while True:
    for col in range(80):
        print('a', end='', flush=True)

This article is part of my complete guide to command-line tools in python.

Previous chapter: How and when to use stdout and stderr in python.