Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
252: The signal Module for Handling OS Signals
Imagine you're a server in a busy restaurant. You're in the middle of taking a complex order from a customer—writing down appetizers, mains, and drinks. Suddenly, your manager taps you on the shoulder and whispers, "Table 4 needs their check right now."
You don't just freeze in place or drop your notepad and walk away. Instead, you acknowledge the manager, quickly handle the request (or make a mental note to do it the second you finish the current sentence), and then get back to the order you were taking. You've essentially "handled" an interrupt without crashing your current task.
In the world of operating systems, that manager's tap is a signal. Your Python program is the server. By default, when Python receives certain signals—like when you hit Ctrl+C in your terminal—it just panics and throws a KeyboardInterrupt. But using the signal module, you can tell Python: "When you feel this specific tap on the shoulder, don't crash; run this specific function instead."
Tapping your program on the shoulder
To make this work, we use signal.signal(). This function takes two arguments: the signal you're looking for and the "handler" (a function) you want to run when that signal arrives. The most common signal you'll encounter is SIGINT, which is what the OS sends when you trigger an interrupt (like Ctrl+C).
import signal
import time
import sys
def graceful_exit(signum, frame):
print(f"\nReceived signal {signum}. Cleaning up my desk before I leave...")
# This is where you'd close database connections or save state
sys.exit(0)
# We tell the OS: "If you send SIGINT, run the graceful_exit function"
signal.signal(signal.SIGINT, graceful_exit)
print("I'm doing some heavy lifting. Try hitting Ctrl+C!")
while True:
time.sleep(1)
print("Still working...", end="\r")
Notice that the graceful_exit function takes two arguments: signum (the signal number) and frame (the current stack frame). You might not always use the frame argument, but Python insists on passing it, so you have to include it in your signature or your code will crash the moment the signal hits.
Handling the "Termination" request
While SIGINT is for humans hitting keys, SIGTERM is what the OS or a process manager (like Docker or systemd) sends when it wants your program to shut down politely. It's basically saying, "I'm going to kill you in a few seconds, so please wrap things up now."
I've seen far too many developers ignore SIGTERM. When they do, the OS eventually loses patience and sends SIGKILL, which is the equivalent of a sudden power outage—no cleanup, no saved files, just an immediate stop. I highly recommend mapping both SIGINT and SIGTERM to the same cleanup function in any production-grade daemon or background worker you write.
The main thread limitation
Here is a quirk that catches people off guard: signals can only be caught by the main thread.
If you've spun up a bunch of threading.Thread workers, they cannot register their own signal handlers. If a signal arrives, the main thread is the only one that can "feel" it. If you need your worker threads to shut down, your main thread's signal handler should set a global threading.Event or a flag that the workers check periodically. It's a bit of extra plumbing, but it's the only way to ensure your entire application shuts down cleanly.
📋 Practical Task
Build a Signal-Aware File Processor
You are building a script that simulates processing a large batch of data. To prevent data corruption, the script creates a "lock file" called processing.lock when it starts. If the script is killed abruptly, that lock file stays on the disk, preventing the script from running again until someone deletes it manually.
Your Task: Write a Python script that does the following:
- Creates a file named
processing.lockat the start of execution. - Enters an infinite loop simulating work (use
time.sleep()). - Implements a signal handler for both
signal.SIGINTandsignal.SIGTERM. - The handler must:
- Print a message saying "Shutting down and removing lock file...".
- Delete the
processing.lockfile using theosmodule. - Exit the program cleanly.
Test your solution by running the script and interrupting it with Ctrl+C. Verify that the processing.lock file is gone after the program exits.
There are no comments for now.