Skip to main content

How to Create a Custom KeyboardInterrupt in Python

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):
# Simulate work
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.")

# Save whatever progress was made
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)

# Register the custom handler
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.
Signal Handler Parameters

The signal handler function receives two arguments:

  • signum: The signal number (2 for SIGINT)
  • frame: The current stack frame (useful for debugging)

You must define both parameters even if you don't use them.

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)
Platform Limitation

signal.SIGALRM and signal.alarm() are not available on Windows. For cross-platform code, use the try-except approach or implement a timer-based solution using threading.

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


# Usage
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​

ApproachComplexityCross-PlatformFinishes Current WorkBest For
Try-except blockSimpleāœ…āŒ (interrupts immediately)Basic cleanup on exit
Signal handlerModeratešŸ”¶ (partial on Windows)āœ… (with flag pattern)Advanced control, servers
Flag-based (should_exit)Moderateāœ…āœ…Long-running loops, workers
Custom exceptionModerateāœ…āŒ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.