← Python Code Python's Hidden Traps
Browse Python Concepts

Context Managers — What with Actually Does

Mental Model

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.

Rule: Always wrap system resources like files, sockets, locks, and DB connections in context managers to guarantee cleanup.

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?

Broken code
Python
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")
Predict what happens to the lock state. Does the block guarantee that the lock is released when the exception is raised inside the with block?

The Output

What actually happens
Lock acquired Running transaction... Lock released Handled runtime error outside context

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

Corrected pattern
Python
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'.

Key Takeaway

Always wrap system resources like files, sockets, locks, and DB connections in context managers to guarantee cleanup.
Common mistake: Developers fail to wrap resources like files, locks, or network connections in context managers, leading to resource leaks if errors occur before explicit cleanup.