Skip to content

How to Capture SIGINT in Python

Learn how to handle Ctrl+C interrupts in Python using try/except KeyboardInterrupt or the signal module for graceful shutdowns, cleanup operations, and controlled program termination.

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.

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)

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:

Parameter Description
signum The signal number (2 for SIGINT)
frame The 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...")
        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

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)

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

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

signal.signal(signal.SIGINT, signal.SIG_DFL)

Quick Reference

Approach Best For
try/except KeyboardInterrupt Simple scripts, quick handling
signal.signal(signal.SIGINT, handler) Production apps, custom cleanup
Flag-based shutdown Completing current work before exit
Double Ctrl+C pattern User-friendly forced exit option

Common Signals Reference

Signal Number Trigger Default Action
SIGINT 2 Ctrl+C Terminate
SIGTERM 15 kill command Terminate
SIGHUP 1 Terminal closed Terminate
SIGALRM 14 alarm() timer Terminate

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.

Navigation