Skip to main content

How to Capture SIGINT in Python

SIGINT (Signal Interrupt) is the signal sent to a program when a user presses Ctrl+C (or Ctrl+F2 on some systems). By default, this terminates the program immediately. However, Python allows you to capture this signal and execute custom cleanup logic, such as saving data, closing connections, or printing a graceful shutdown message, before the program exits.

This guide covers multiple ways to handle SIGINT in Python, from simple try/except blocks to the more powerful signal module.

Using try/except KeyboardInterrupt (Simplest)โ€‹

The easiest way to capture Ctrl+C is wrapping your code in a try/except block that catches KeyboardInterrupt:

import sys
from time import sleep

counter = 1

while True:
try:
print(f"Running... count: {counter}")
sleep(0.5)
counter += 1
except KeyboardInterrupt:
print("\nโœ… Program stopped gracefully by user.")
sys.exit(0)

Output (after pressing Ctrl+C):

Running... count: 1
Running... count: 2
Running... count: 3
^C
โœ… Program stopped gracefully by user.
note

This approach is straightforward and works well for simple scripts. The KeyboardInterrupt exception is raised whenever the user presses Ctrl+C.

Using the signal Moduleโ€‹

For more control over signal handling, Python's signal module lets you register a custom handler function that runs when SIGINT is received. This is the standard approach for production applications.

import signal
import sys
from time import sleep

def handle_sigint(signum, frame):
"""Custom handler for SIGINT (Ctrl+C)."""
print(f"\nโœ… Received signal {signum} (SIGINT)")
print("Performing cleanup before exit...")
sys.exit(0)

# Register the custom signal handler
signal.signal(signal.SIGINT, handle_sigint)

print("Program is running. Press Ctrl+C to stop.")
counter = 1

while True:
print(f"Working... iteration {counter}")
sleep(1)
counter += 1

Output (after pressing Ctrl+C):

Program is running. Press Ctrl+C to stop.
Working... iteration 1
Working... iteration 2
Working... iteration 3
^C
โœ… Received signal 2 (SIGINT)
Performing cleanup before exit...

Understanding the Handler Parametersโ€‹

The signal handler function receives two arguments:

ParameterDescription
signumThe signal number (2 for SIGINT)
frameThe current stack frame (useful for debugging)
import signal
import sys

def handler(signum, frame):
print(f"\nSignal number: {signum}")
print(f"Interrupted at: {frame.f_code.co_filename}, line {frame.f_lineno}")
sys.exit(0)

signal.signal(signal.SIGINT, handler)

Practical Example: Graceful Shutdown with Cleanupโ€‹

A real-world use case is performing cleanup operations before shutting down, i.e. saving files, closing database connections, or flushing logs:

import signal
import sys
from time import sleep

class DataProcessor:
def __init__(self):
self.data = []
self.running = True

def process(self):
"""Simulate data processing."""
counter = 0
while self.running:
counter += 1
self.data.append(f"record_{counter}")
print(f"Processed record {counter}")
sleep(0.5)

def cleanup(self):
"""Save data and release resources."""
print(f"\n๐Ÿ“ฆ Saving {len(self.data)} records...")
# Simulate saving to file
with open("output.txt", "w") as f:
f.write("\n".join(self.data))
print("โœ… Data saved to output.txt")
print("๐Ÿ”’ Closing connections...")
print("๐Ÿ‘‹ Shutdown complete.")

def stop(self):
"""Signal the processor to stop."""
self.running = False


processor = DataProcessor()

def handle_shutdown(signum, frame):
processor.stop()
processor.cleanup()
sys.exit(0)

signal.signal(signal.SIGINT, handle_shutdown)

print("Data processor started. Press Ctrl+C to stop.\n")
processor.process()

Output (after pressing Ctrl+C):

Data processor started. Press Ctrl+C to stop.

Processed record 1
Processed record 2
Processed record 3
^C
๐Ÿ“ฆ Saving 3 records...
โœ… Data saved to output.txt
๐Ÿ”’ Closing connections...
๐Ÿ‘‹ Shutdown complete.

Using a Flag for Controlled Shutdownโ€‹

Instead of calling sys.exit() immediately, you can set a flag that allows the main loop to finish its current iteration gracefully:

import signal
from time import sleep

shutdown_requested = False

def handle_sigint(signum, frame):
global shutdown_requested
print("\nโš ๏ธ Shutdown requested. Finishing current task...")
shutdown_requested = True

signal.signal(signal.SIGINT, handle_sigint)

print("Processing tasks. Press Ctrl+C to stop after current task.\n")

for i in range(1, 100):
if shutdown_requested:
print(f"โœ… Stopped after completing task {i - 1}")
break

print(f"Task {i}: processing...")
sleep(1) # Simulate work
print(f"Task {i}: complete")

Output (pressing Ctrl+C during task 3):

Processing tasks. Press Ctrl+C to stop after current task.

Task 1: processing...
Task 1: complete
Task 2: processing...
Task 2: complete
Task 3: processing...
^C
โš ๏ธ Shutdown requested. Finishing current task...
Task 3: complete
โœ… Stopped after completing task 3
tip

The flag-based approach ensures that the current operation completes before shutting down, preventing data corruption or partial writes.

Handling Multiple Ctrl+C Pressesโ€‹

Sometimes users press Ctrl+C multiple times. You can handle this by counting signals or forcing exit on the second press:

import signal
import sys
from time import sleep

sigint_count = 0

def handle_sigint(signum, frame):
global sigint_count
sigint_count += 1

if sigint_count == 1:
print("\nโš ๏ธ Press Ctrl+C again to force exit.")
print(" Attempting graceful shutdown...")
else:
print("\nโŒ Force exit!")
sys.exit(1)

signal.signal(signal.SIGINT, handle_sigint)

print("Running... Press Ctrl+C to stop.\n")

while sigint_count == 0:
sleep(0.5)

# Graceful shutdown
print("๐Ÿงน Cleaning up...")
sleep(2)
print("โœ… Done.")

Restoring the Default Handlerโ€‹

You can restore the default SIGINT behavior (immediate termination) by setting the handler back to signal.SIG_DFL:

import signal

# Custom handler
signal.signal(signal.SIGINT, lambda s, f: print("\nIgnored!"))

# Restore default behavior
signal.signal(signal.SIGINT, signal.SIG_DFL)

Quick Referenceโ€‹

ApproachBest For
try/except KeyboardInterruptSimple scripts, quick handling
signal.signal(signal.SIGINT, handler)Production apps, custom cleanup
Flag-based shutdownCompleting current work before exit
Double Ctrl+C patternUser-friendly forced exit option

Common Signals Referenceโ€‹

SignalNumberTriggerDefault Action
SIGINT2Ctrl+CTerminate
SIGTERM15kill commandTerminate
SIGHUP1Terminal closedTerminate
SIGALRM14alarm() timerTerminate
note

On Windows, only SIGINT, SIGTERM, SIGABRT, and SIGBREAK are supported. Linux and macOS support the full range of POSIX signals.

Conclusionโ€‹

Capturing SIGINT in Python lets you handle Ctrl+C gracefully instead of abruptly terminating your program. For simple scripts, a try/except KeyboardInterrupt block is sufficient. For production applications that need cleanup operations, resource management, or controlled shutdown, use the signal module to register a custom handler. The flag-based approach is ideal when you need to ensure the current operation completes before shutting down. Whichever method you choose, graceful signal handling makes your Python programs more robust and user-friendly.