Skip to content

How to Create a Custom KeyboardInterrupt in Python

Learn how to handle Ctrl+C gracefully in Python by catching KeyboardInterrupt with try-except blocks, signal handlers, or custom exceptions to save progress, clean up resources, and exit cleanly.

When a user presses Ctrl+C during program execution, Python raises a KeyboardInterrupt exception that terminates the program abruptly. While this default behavior is useful, there are many situations where you need more control, saving progress before exiting, closing database connections, releasing file locks, or simply displaying a friendly message.

Creating a custom KeyboardInterrupt handler lets you define exactly what happens when the user interrupts your program. In this guide, you'll learn two approaches to handle keyboard interrupts gracefully in Python.

Why Customize KeyboardInterrupt?

By default, pressing Ctrl+C produces an ugly traceback and immediately kills the program:

^CTraceback (most recent call last):
  File "script.py", line 5, in <module>
    time.sleep(1)
KeyboardInterrupt

A custom handler lets you:

  • Save progress before exiting (e.g., partially processed data)
  • Close resources cleanly (files, database connections, network sockets)
  • Display a user-friendly message instead of a traceback
  • Perform cleanup operations like removing temporary files
  • Ask for confirmation before actually terminating

Using a Try-Except Block

The most straightforward approach catches KeyboardInterrupt in a try/except block and executes custom cleanup code:

import time

def cleanup():
    """Perform cleanup operations before exiting."""
    print("\n⚠️  Interrupt received! Cleaning up...")
    print("  → Saving progress...")
    print("  → Closing connections...")
    print("  → Cleanup complete.")

try:
    print("Program running. Press Ctrl+C to stop.")
    counter = 0
    while True:
        counter += 1
        print(f"  Processing item {counter}...", end='\r')
        time.sleep(1)

except KeyboardInterrupt:
    cleanup()

print("Program terminated gracefully.")

Output (after pressing Ctrl+C):

Program running. Press Ctrl+C to stop.
  Processing item 3...
⚠️  Interrupt received! Cleaning up...
  → Saving progress...
  → Closing connections...
  → Cleanup complete.
Program terminated gracefully.

The program catches the interrupt, runs the cleanup function, and exits cleanly without any traceback.

Practical Example: Saving Data on Interrupt

import time
import json

def process_data():
    """Simulate a long-running data processing task."""
    results = []

    try:
        print("Processing data... Press Ctrl+C to stop and save progress.\n")
        for i in range(1, 101):
            time.sleep(0.5)
            results.append({"item": i, "status": "processed"})
            print(f"  Processed {i}/100 items", end='\r')

    except KeyboardInterrupt:
        print(f"\n\n⚠️  Interrupted after processing {len(results)} items.")

        with open('progress.json', 'w') as f:
            json.dump(results, f, indent=2)

        print(f"  → Progress saved to progress.json ({len(results)} items)")
        return results

    print("\n✅ All items processed successfully!")
    return results


data = process_data()

Output (after pressing Ctrl+C):

Processing data... Press Ctrl+C to stop and save progress.

  Processed 7/100 items
⚠️  Interrupted after processing 7 items.
  → Progress saved to progress.json (7 items)

Using a Signal Handler

For more advanced control, use Python's signal module to register a handler for the SIGINT signal (which is what Ctrl+C sends). This approach intercepts the signal before Python raises KeyboardInterrupt.

import signal
import time

def handle_interrupt(signum, frame):
    """Custom handler for SIGINT (Ctrl+C)."""
    print(f"\n⚠️  Received signal {signum} (SIGINT)")
    print("  → Performing graceful shutdown...")
    print("  → Shutdown complete.")
    exit(0)

signal.signal(signal.SIGINT, handle_interrupt)

print("Program running. Press Ctrl+C to stop.")

while True:
    print("  Working...", end='\r')
    time.sleep(1)

Output (after pressing Ctrl+C):

Program running. Press Ctrl+C to stop.
  Working...
⚠️  Received signal 2 (SIGINT)
  → Performing graceful shutdown...
  → Shutdown complete.

Requiring Double Ctrl+C to Exit

A common pattern is requiring the user to press Ctrl+C twice to confirm they want to exit:

import signal
import time

interrupt_count = 0

def handle_interrupt(signum, frame):
    global interrupt_count
    interrupt_count += 1

    if interrupt_count == 1:
        print("\n⚠️  Press Ctrl+C again within 3 seconds to confirm exit.")
        signal.alarm(3)  # Reset after 3 seconds (Unix only)
    else:
        print("\n👋 Confirmed. Exiting...")
        exit(0)

def reset_counter(signum, frame):
    global interrupt_count
    interrupt_count = 0

signal.signal(signal.SIGINT, handle_interrupt)
signal.signal(signal.SIGALRM, reset_counter)  # Unix only

print("Program running. Press Ctrl+C twice to stop.")

while True:
    time.sleep(1)

Combining Both Approaches

For maximum robustness, combine signal handling with try-except as a fallback:

import signal
import time

class GracefulShutdown:
    """Handle keyboard interrupts gracefully."""

    def __init__(self):
        self.should_exit = False
        signal.signal(signal.SIGINT, self._handle_signal)

    def _handle_signal(self, signum, frame):
        print("\n⚠️  Shutdown requested...")
        self.should_exit = True

    def is_running(self):
        return not self.should_exit


shutdown = GracefulShutdown()

print("Program running. Press Ctrl+C to stop gracefully.\n")

counter = 0
while shutdown.is_running():
    counter += 1
    print(f"  Completed iteration {counter}")
    time.sleep(1)

print(f"\n✅ Graceful shutdown after {counter} iterations.")
print("  → Resources released.")

Output (after pressing Ctrl+C):

Program running. Press Ctrl+C to stop gracefully.

  Completed iteration 1
  Completed iteration 2
  Completed iteration 3
⚠️  Shutdown requested...

✅ Graceful shutdown after 3 iterations.
  → Resources released.

This approach lets the current iteration finish before exiting, rather than interrupting mid-operation.

Creating a Custom Exception Class

You can define a custom exception that wraps KeyboardInterrupt with additional context:

import time

class GracefulExit(Exception):
    """Custom exception for graceful program termination."""
    def __init__(self, message="Program interrupted by user"):
        self.message = message
        super().__init__(self.message)


def run_task():
    try:
        print("Running task... Press Ctrl+C to stop.\n")
        for i in range(1, 100):
            print(f"  Step {i}/100")
            time.sleep(0.5)

    except KeyboardInterrupt:
        raise GracefulExit(f"Task interrupted at step {i}")


try:
    run_task()
except GracefulExit as e:
    print(f"\n⚠️  {e.message}")
    print("  → Performing cleanup...")
    print("  → Done.")

Output (after pressing Ctrl+C):

Running task... Press Ctrl+C to stop.

  Step 1/100
  Step 2/100
  Step 3/100
⚠️  Task interrupted at step 3
  → Performing cleanup...
  → Done.

Quick Comparison of Approaches

Approach Complexity Cross-Platform Finishes Current Work Best For
Try-except block Simple ✅ ❌ (interrupts immediately) Basic cleanup on exit
Signal handler Moderate 🔶 (partial on Windows) ✅ (with flag pattern) Advanced control, servers
Flag-based (should_exit) Moderate ✅ ✅ Long-running loops, workers
Custom exception Moderate ✅ ❌ Adding context to interrupts

Conclusion

Customizing KeyboardInterrupt handling in Python ensures your programs exit cleanly and predictably:

  • Use a try-except block for simple scripts that need basic cleanup. It's the easiest approach and works everywhere.
  • Use a signal handler for more advanced control, such as requiring confirmation or handling multiple signal types.
  • Use the flag-based pattern (should_exit) when you need the current operation to finish before shutting down, ideal for data processing loops and server applications.
  • Use a custom exception class when you want to propagate interrupt context up the call stack.

For most applications, the try-except approach provides the right balance of simplicity and functionality. For long-running services or data pipelines, the flag-based signal handler pattern ensures no work is lost during shutdown.

Navigation