Context managers handle setup and teardown automatically. Here's everything you need to know.
The Basics
# Without context manager
file = open("data.txt")
try:
content = file.read()
finally:
file.close()
# With context manager
with open("data.txt") as file:
content = file.read()
# File is automatically closedThe with statement ensures cleanup happens even if an exception occurs.
Common Use Cases
Files:
with open("output.txt", "w") as f:
f.write("Hello, World!")Database connections:
with sqlite3.connect("database.db") as conn:
cursor = conn.cursor()
cursor.execute("SELECT * FROM users")Locks:
import threading
lock = threading.Lock()
with lock:
# Critical section
shared_resource.modify()HTTP sessions:
import requests
with requests.Session() as session:
response = session.get("https://api.example.com")How It Works
Context managers implement two methods:
class MyContext:
def __enter__(self):
# Setup code
print("Entering context")
return self # Value bound to 'as' variable
def __exit__(self, exc_type, exc_val, exc_tb):
# Cleanup code
print("Exiting context")
return False # Don't suppress exceptions
with MyContext() as ctx:
print("Inside context")
# Output:
# Entering context
# Inside context
# Exiting contextThe __exit__ method receives exception info if one occurred:
exc_type: Exception class (or None)exc_val: Exception instance (or None)exc_tb: Traceback (or None)
Return True from __exit__ to suppress the exception.
Using contextlib
The contextlib module provides shortcuts.
@contextmanager decorator
Turn a generator into a context manager:
from contextlib import contextmanager
@contextmanager
def timer(name):
import time
start = time.time()
try:
yield # Code inside 'with' block runs here
finally:
elapsed = time.time() - start
print(f"{name} took {elapsed:.2f}s")
with timer("Processing"):
# Do work
process_data()Everything before yield is __enter__, everything after is __exit__.
Passing values
@contextmanager
def temp_directory():
import tempfile
import shutil
path = tempfile.mkdtemp()
try:
yield path # Pass the path to the with block
finally:
shutil.rmtree(path)
with temp_directory() as tmpdir:
# Use tmpdir
save_file(f"{tmpdir}/data.txt")
# Directory is deleted aftersuppress()
Ignore specific exceptions:
from contextlib import suppress
with suppress(FileNotFoundError):
os.remove("maybe_exists.txt")
# No error if file doesn't existredirect_stdout/redirect_stderr
from contextlib import redirect_stdout
from io import StringIO
buffer = StringIO()
with redirect_stdout(buffer):
print("This goes to buffer")
output = buffer.getvalue()Nesting Context Managers
Multiple managers in one with:
with open("input.txt") as infile, open("output.txt", "w") as outfile:
outfile.write(infile.read().upper())Or use ExitStack for dynamic nesting:
from contextlib import ExitStack
with ExitStack() as stack:
files = [
stack.enter_context(open(f"file{i}.txt"))
for i in range(10)
]
# All files are open
# All files are closedReal-World Example
A database transaction manager:
from contextlib import contextmanager
@contextmanager
def transaction(connection):
cursor = connection.cursor()
try:
yield cursor
connection.commit()
except Exception:
connection.rollback()
raise
finally:
cursor.close()
# Usage
with transaction(db_conn) as cursor:
cursor.execute("INSERT INTO users VALUES (?)", (name,))
cursor.execute("UPDATE stats SET count = count + 1")
# Auto-commits on success, rolls back on errorAsync Context Managers
For async code, use async with:
class AsyncResource:
async def __aenter__(self):
await self.connect()
return self
async def __aexit__(self, *args):
await self.disconnect()
async with AsyncResource() as resource:
await resource.do_work()Or with @asynccontextmanager:
from contextlib import asynccontextmanager
@asynccontextmanager
async def async_timer(name):
start = time.time()
try:
yield
finally:
print(f"{name}: {time.time() - start:.2f}s")When to Use Context Managers
Use them when you have:
- Resources that need cleanup (files, connections, locks)
- Setup/teardown pairs
- Temporary state changes
- Transaction-like operations
They make code cleaner and prevent resource leaks. If you're writing try/finally for cleanup, consider a context manager instead.