Context Managers — What with Actually Does
A context manager (the 'with' statement) is like a reliable doorman for a VIP lounge. As you enter ('__enter__'), the doorman handles all the setup (e.g., acquiring a lock). As you exit ('__exit__'), regardless of whether you leave politely or get thrown out (exception), the doorman always handles the cleanup (e.g., releasing the lock), ensuring the lounge is ready for the next guest.
The Setup
You are writing a database connection locker that locks a global state table, performs a transaction, and unlocks it. If an unexpected exception occurs inside the transaction block, the system must unlock the database immediately to avoid deadlocks.
What Does This Print?
class LockManager:
def __init__(self):
self.locked = False
def __enter__(self):
self.locked = True
print("Lock acquired")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.locked = False
print("Lock released")
try:
with LockManager() as lm:
print("Running transaction...")
raise RuntimeError("Database connection dropped")
except RuntimeError:
print("Handled runtime error outside context")
The Output
The lock is successfully released. Python automatically calls the __exit__ method of the context manager when exiting the with block, regardless of whether the block exited normally or via an unhandled exception.
Why Python Does This
The with statement translates directly to a SETUP_WITH bytecode instruction. When the virtual machine enters the block, it evaluates the expression, retrieves the context manager, and invokes its __enter__ method. The VM wraps the body of the with statement in an implicit try-finally mechanism. If an exception occurs, Python passes the exception class, instance, and traceback to __exit__. If __exit__ returns a truthy value, the exception is suppressed; if it returns False or None, the exception is re-raised.
The Fix
from contextlib import contextmanager
# Creating a cleaner generator-based context manager using contextlib
@contextmanager
def db_lock_session():
print("Lock acquired")
try:
yield
finally:
# Guarantees cleanup even if code inside context raises exceptions
print("Lock released")
The 'with' statement guarantees that the '__exit__' method of the context manager is called upon exiting the block, whether normally or due to an exception. This deterministic cleanup mechanism ensures resources are properly released, preventing leaks and improving reliability.
How This Fails in Real Systems
A file-upload processing worker opened file streams without using context managers. Under heavy load, exceptions were occasionally raised while reading corruption metadata, leaving hundreds of ghost file descriptors open. Within two hours, the containerized application crashed with 'OSError: [Errno 24] Too many open files'.