Python Signal Library – Signal Handling for Events

The Python Signal Library allows us to interact with OS level signals for asynchronous events and handle them with custom Signal Handlers. For example, if we press CTRL + C during a programs execution, this terminates the programs execution. This is what we call an event.

Alongside these events, the OS generates a “signal” to let us know the event has occurred. The signal module in Python allows us to over-ride the default behaviors of these events with our own custom Signal Handling.


Introduction to Python Signals

These are the signals currently available on Windows (as of Python 3.10). If you are using a Unix-based Operating System, such as MacOS or Linux, you can expect to see more Signals available to you (Windows doesn’t support them all).

We can use the following code snippet to print out a list of available signals on our OS. Each signal also has a unique integer ID, which can be used to identify it.

import signal
 
valid_signals = signal.valid_signals()
print(valid_signals)
{<Signals.SIGINT: 2>, <Signals.SIGILL: 4>, <Signals.SIGFPE: 8>, <Signals.SIGSEGV: 11>, <Signals.SIGTERM: 15>, <Signals.SIGBREAK: 21>, <Signals.SIGABRT: 22>}

Note: There are some signals which are available only on Windows.

For a complete list of signals, and their respective descriptions, you should refer to the actual documentation for Python signals.


Using Signal Handlers

As we mentioned earlier, you can “handle” a signal to change its default behavior. Lets use SIGINT, which triggers when you call CTRL + C (during a programs execution).

In order to “handle” a signal like SIGINT, we need to use the signal() function from the signal module as shown below.

signal.signal(signal.SIGINT, signal_handler)

The first parameter is the signal we want to handle, and the second parameter is the name of the function we wish to handle it with. This function is automatically passed two parameters, the first being the signal ID, and the second is the frame details.

If we trigger SIGINT in its default state, the program will terminate. Let us try and change this behavior, and make it simply print out a regular statement.

import signal  
import time 

# Our signal handler
def signal_handler(signum, frame):  
    print("Signal ID:", signum, " Frame: ", frame)  
 
# Handling SIGINT 
signal.signal(signal.SIGINT, signal_handler)
 
time.sleep(5)

The above code will make the program sleep for 5 seconds. This will not prevent you from triggering CTRL + C (SIGINT) however, as it is an asynchronous event. During these 5 seconds, if we press the CTRL + C keys, then we get the following output.

Signal ID: 2  Frame:  <frame at 0x0000024AFFA09C40, file 'c:\\Desktop\\Code\\signals.py', line 11, code <module>>

It is also important to note, that CTRL + C will not close our program, because we have changed its behavior.


Advanced Signal Handling in Python

There are many more functions available in the signal() library which allow us to easily trigger and better handle signals. Let us discuss some of the more important ones, as well as some other signals available to us.

A cool thing that we can do, is preserve the original_handler for the SIGINT function. Whenever you assign a custom signal using the signal() function, there is actually a function returned, which can be used to call the original handler.

We can either use this to eventually restore SIGINT to its default behavior (by passing it into the signal function), or we can use it as a regular function inside our signal hander, allowing us to use custom behavior along with the default behavior.

import signal  
import time 

# Our signal handler
def signal_handler(signum, frame):  
    global original_handler
    print("Signal ID:", signum, " Frame: ", frame)  
    original_handler(signum, frame)

original_handler = signal.signal(signal.SIGINT, signal_handler)

print("Waiting for 5 seconds...")
time.sleep(5)

Try running the above code. You will get both a print statement (custom behavior), and a keyboard interrupt (default behavior).

Another handy function we can use is the alarm() function, which generates a SIGALRM signal. What makes this function so special though? Well, as it turns out we can choose to have SIGALRM triggered after a certain interval (like after 5 seconds). This gives us alot of opportunity to schedule events and functions.

import signal
import time

def signal_handler(signum, stack):
    print('Alarm: ', time.ctime())

signal.signal(signal.SIGALRM, signal_handler)
signal.alarm(5)
time.sleep(10)

The time.sleep() call is necessary, otherwise our program would end before SIGALRM had a change to trigger.

Note: Sadly, this signal is not available on Windows. (Only on Unix)


This marks the end of the Python Signal Library Tutorial. Any suggestions or contributions for CodersLegacy are more than welcome. Questions regarding the tutorial content can be asked in the comments section below.