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.
The signal handler function receives two arguments:
signum: The signal number (2 forSIGINT)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)
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ā
| 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.