signal

import signal
Added in v1.0 · Updated March 13, 2026 · Modules
signal unix process signal-handling

The signal module provides a way to handle asynchronous events in Python. Signals are notifications sent to a process by the operating system to indicate that something has happened—like a user pressing Ctrl+C, a timer expiring, or a child process terminating. This module lets you define custom handlers that run when specific signals arrive.

The module works primarily on Unix systems. Windows has limited signal support (only SIGINT, SIGTERM, SIGBREAK, SIGABRT, and SIGFPE are available). On Windows, signal.signal() can only be called with these signals, and functions like alarm() and pause() are not available.

Python installs default handlers for a few signals automatically. SIGPIPE is ignored (so write errors on pipes raise exceptions instead of killing your program), and SIGINT is translated to KeyboardInterrupt if the parent process hasn’t changed it.

Important Constraints

Python signal handlers have some quirks worth knowing about:

  1. Handlers run in the main Python thread, even if the signal arrived in another thread. You cannot use signals for inter-thread communication.

  2. Signal handlers don’t execute immediately when a signal arrives—they execute at the next Python bytecode instruction. This means a long-running C extension (like a regex on a large text) will run uninterrupted until it returns to Python.

  3. If a signal handler raises an exception, it appears “out of thin air” in the main thread. This can happen between any two bytecode instructions, which is why complex code (especially code using locks) can behave unexpectedly when signals are involved.

  4. Only the main thread can set signal handlers. Calling signal.signal() from any other thread raises ValueError.

signal.signal()

Register a handler function to be called when a signal is received.

signal.signal(signalnum, handler)

Parameters:

ParameterTypeDescription
signalnumintSignal number (e.g., signal.SIGINT)
handlercallableHandler function or SIG_DFL/SIG_IGN

The handler must be a callable accepting two arguments: the signal number and the current stack frame. You can also pass signal.SIG_IGN to ignore the signal or signal.SIG_DFL to use the default handler.

import signal
import sys

def handle_interrupt(signum, frame):
    print("\nReceived interrupt signal!")
    print(f"Signal number: {signum}")
    sys.exit(0)

# Register handler for SIGINT (Ctrl+C)
signal.signal(signal.SIGINT, handle_interrupt)

# Now press Ctrl+C to see the handler run
print("Press Ctrl+C to trigger handler...")
import time
time.sleep(60)

Returns: The previous signal handler (callable, SIG_DFL, SIG_IGN, or None).

signal.raise_signal()

Send a signal to the calling process itself. This is useful for testing signal handlers or for self-signaling.

signal.raise_signal(signum)

Parameters:

ParameterTypeDescription
signumintSignal number to send
import signal

def my_handler(signum, frame):
    print(f"Caught signal {signum}")

signal.signal(signal.SIGUSR1, my_handler)

# Send SIGUSR1 to ourselves
signal.raise_signal(signal.SIGUSR1)
# Output: Caught signal 10

Added in Python 3.8.

signal.alarm()

Schedule a SIGALRM signal to be delivered after a specified number of seconds. Only one alarm can be scheduled at a time.

signal.alarm(time)

Parameters:

ParameterTypeDescription
timeintSeconds until alarm (0 cancels any pending alarm)
import signal
import time

def alarm_handler(signum, frame):
    print("Alarm fired! 5 seconds have passed.")

signal.signal(signal.SIGALRM, alarm_handler)

print("Scheduling alarm in 5 seconds...")
signal.alarm(5)

# Do other work while waiting
time.sleep(3)
print("3 seconds later...")

# Wait for the alarm
time.sleep(3)
# Output:
# Scheduling alarm in 5 seconds...
# 3 seconds later...
# Alarm fired! 5 seconds have passed.

Returns: Number of seconds remaining on any previously scheduled alarm, or 0 if no alarm was pending.

Not available on Windows.

signal.pause()

Suspend the process until a signal is received. The registered handler (if any) will be called when the signal arrives.

signal.pause()
import signal
import time

def handler(signum, frame):
    print("Signal received!")

signal.signal(signal.SIGUSR1, handler)

print("Waiting for SIGUSR1... (send with: kill -USR1 <pid>)")
signal.pause()
print("Continuing execution...")

Not available on Windows.

signal.getsignal()

Query the current handler registered for a signal.

signal.getsignal(signalnum)

Parameters:

ParameterTypeDescription
signalnumintSignal number to query

Returns: The current handler—a callable, SIG_IGN, SIG_DFL, or None (if the handler wasn’t installed from Python).

import signal

# Check default handler for SIGINT
handler = signal.getsignal(signal.SIGINT)
print(f"SIGINT handler: {handler}")
# Output: <built-in function default_int_handler>

# Install custom handler
def custom_handler(signum, frame):
    pass

signal.signal(signal.SIGINT, custom_handler)
print(f"After setting: {signal.getsignal(signal.SIGINT)}")
# Output: <function custom_handler at 0x...>

Common Signals

These are the most frequently used signals:

SignalNumberDescription
SIGINT2Interrupt from keyboard (Ctrl+C). Default raises KeyboardInterrupt.
SIGTERM15Termination request. The polite way to ask a process to shut down.
SIGALRM14Timer expired (from alarm() or setitimer()).
SIGUSR110User-defined signal 1. Available for custom use.
SIGUSR212User-defined signal 2. Available for custom use.
SIGKILL9Kill signal. Cannot be caught, blocked, or ignored. Forces immediate termination.
SIGSTOP19Stop signal. Cannot be caught or ignored. Pauses the process.
SIGHUP1Hangup detected on terminal or death of controlling process. Common daemon notification.
SIGCHLD17Child process stopped or terminated. Default is to ignore.
import signal

# List all available signals on this platform
valid = signal.valid_signals()
print(f"Valid signals: {valid}")

# Get human-readable signal name
print(f"SIGINT name: {signal.Signals(signal.SIGINT).name}")
print(f"SIGTERM name: {signal.Signals(signal.SIGTERM).name}")

# Get description
print(f"SIGINT: {signal.strsignal(signal.SIGINT)}")
# Output: Interrupt from keyboard (Ctrl+C)

Examples

Implementing a timeout with alarm

import signal
import time

def timeout_handler(signum, frame):
    raise TimeoutError("Operation timed out")

def slow_operation():
    time.sleep(10)
    return "completed"

# Set 2-second timeout
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(2)

try:
    result = slow_operation()
    print(f"Result: {result}")
except TimeoutError as e:
    print(f"Error: {e}")
finally:
    signal.alarm(0)  # Cancel alarm if operation succeeded

Output:

Error: Operation timed out

Graceful shutdown on SIGTERM

import signal
import sys
import time

shutdown_requested = False

def handle_sigterm(signum, frame):
    global shutdown_requested
    print("Received SIGTERM, shutting down gracefully...")
    shutdown_requested = True

signal.signal(signal.SIGTERM, handle_sigterm)

print("Server running... (send SIGTERM to test)")
while not shutdown_requested:
    # Do work here
    time.sleep(0.1)

print("Cleaned up resources.")
sys.exit(0)

Using user-defined signals for custom events

import signal
import os

def handle_usr1(signum, frame):
    print("Reloading configuration!")

def handle_usr2(signum, frame):
    print("Dumping debug info!")

signal.signal(signal.SIGUSR1, handle_usr1)
signal.signal(signal.SIGUSR2, handle_usr2)

print(f"Running with PID: {os.getpid()}")
print("Send SIGUSR1 to reload, SIGUSR2 to debug")

# In another terminal:
# kill -USR1 <pid>
# kill -USR2 <pid>

Common Patterns

Checking if code is running interactively

import signal
import sys

# Ignore SIGINT in non-interactive contexts
if not sys.stdin.isatty():
    signal.signal(signal.SIGINT, signal.SIG_IGN)

Portable Ctrl+C handling

import signal
import sys

def graceful_shutdown(signum, frame):
    print("\nShutting down...")
    # Clean up resources here
    sys.exit(0)

signal.signal(signal.SIGINT, graceful_shutdown)
signal.signal(signal.SIGTERM, graceful_shutdown)

Block and unblock signals (Unix)

import signal

# Block SIGUSR1 temporarily
old_mask = signal.pthread_sigmask(signal.SIG_BLOCK, {signal.SIGUSR1})

# SIGUSR1 is now blocked
# Do critical work...

# Restore previous mask
signal.pthread_sigmask(signal.SIG_SETMASK, old_mask)

See Also