Using context managers in Python is the idiomatic way to manage resources that need explicit setup and teardown — files, database connections, locks, network sockets, temporary directories, thread pools, and more — ensuring they are always properly released, even when exceptions occur. The with statement automatically calls __enter__() on entry and __exit__() on exit, handling cleanup reliably. In 2026, context managers remain a cornerstone of robust, exception-safe Python code — essential in data pipelines (file I/O, database transactions), web scraping (sessions, browsers), concurrency (locks), testing (mocks, patches), and production systems where resource leaks cause crashes, deadlocks, or memory exhaustion. They make code cleaner, safer, and more readable than manual try/finally blocks.
Here’s a complete, practical guide to context managers in Python: built-in examples, writing your own (class-based and generator-based), real-world patterns, and modern best practices with type hints, multiple contexts, contextlib tools, and pandas/Polars integration.
Built-in context managers — with open(), with lock:, with connection: — handle common resources safely.
# File handling — auto-close even on exception
with open('data.txt', 'r') as f:
content = f.read() # f is open here
# f is closed automatically here, even if read() raised an error
# Thread lock — acquire/release automatically
from threading import Lock
lock = Lock()
with lock:
# critical section — lock held here
shared_counter += 1
# lock released automatically
Write your own class-based context manager — implement __enter__() (setup, return resource) and __exit__() (cleanup, handle exceptions if needed).
class DatabaseConnection:
def __init__(self, db_url: str):
self.db_url = db_url
self.conn = None
def __enter__(self):
self.conn = connect_to_db(self.db_url) # setup
return self.conn # return resource to 'as' variable
def __exit__(self, exc_type, exc_val, exc_tb):
if self.conn:
self.conn.close() # cleanup
if exc_type is not None:
print(f"Exception occurred: {exc_val}")
return False # do not suppress exception (re-raise)
# Usage
with DatabaseConnection("sqlite:///mydb.db") as conn:
results = conn.execute("SELECT * FROM users")
Generator-based context managers (recommended for most cases) — use @contextmanager from contextlib — simpler, no class needed.
from contextlib import contextmanager
@contextmanager
def temp_file(content: str):
import tempfile
with tempfile.NamedTemporaryFile(mode='w+', delete=False) as tmp:
tmp.write(content)
tmp.flush()
yield tmp.name # provide path to user
# cleanup happens automatically after yield block exits
import os
os.unlink(tmp.name)
# Usage
with temp_file("Hello, temp!") as path:
with open(path) as f:
print(f.read()) # Hello, temp!
# file is deleted automatically
Real-world pattern: safe resource management in pandas/Polars pipelines — context managers for files, connections, or temporary states.
from contextlib import contextmanager
import pandas as pd
@contextmanager
def safe_csv_read(path: str):
try:
df = pd.read_csv(path)
yield df
finally:
print(f"CSV read completed for {path}")
with safe_csv_read('large_data.csv') as df:
cleaned = df.dropna().groupby('category').sum()
print(cleaned.head())
Best practices make context managers safe, readable, and performant. Prefer generator-based (@contextmanager) over class-based — simpler, less boilerplate, exception handling automatic. Use multiple contexts with nested with or parentheses — with A() as a, B() as b:. Modern tip: use Polars for file I/O — pl.read_csv(...) with contextlib for custom cleanup. Add type hints — def func() -> ContextManager[Resource] — improves clarity and mypy checks. Return meaningful resource from __enter__/yield — usually self or opened resource. Handle exceptions in __exit__ or after yield — suppress with return True only when appropriate. Use contextlib.ExitStack for dynamic contexts — add resources conditionally. Use contextlib.suppress() for ignoring specific exceptions. Combine with try/finally only when context manager isn’t feasible. Use tempfile, contextlib.nullcontext, contextlib.redirect_stdout for utilities. Test context managers — ensure resource released with assert not resource.open after block.
Context managers with with ensure resources are acquired and released safely — built-in for files/locks, custom class-based or generator-based for anything else. In 2026, prefer @contextmanager, nested/multiple with, defensive cleanup, type hints, and Polars for I/O. Master context managers, and you’ll write exception-safe, readable, resource-efficient Python code that never leaks files, connections, or locks.
Next time you open a file, connect to a database, or acquire a lock — use a context manager. It’s Python’s cleanest way to say: “Acquire this resource, use it, and guarantee cleanup — no matter what.”