Context managers handle setup and cleanup automatically. Here's how they work.
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 automatically closedThe with statement guarantees cleanup, even if exceptions occur.
How It Works
Context managers implement two methods:
class MyContext:
def __enter__(self):
# Setup - runs at start of with block
print("Entering")
return self # Value bound to 'as' variable
def __exit__(self, exc_type, exc_val, exc_tb):
# Cleanup - runs at end of with block
print("Exiting")
# Return True to suppress exceptions
return False
with MyContext() as ctx:
print("Inside")
# Output:
# Entering
# Inside
# ExitingCommon Use Cases
File handling
with open("data.txt", "w") as f:
f.write("Hello")
# File closed automaticallyDatabase connections
with get_connection() as conn:
cursor = conn.cursor()
cursor.execute("SELECT * FROM users")
# Connection closed/returned to poolLocks
import threading
lock = threading.Lock()
with lock:
# Critical section
shared_resource.update()
# Lock released automaticallyTemporary changes
import os
@contextmanager
def change_dir(path):
old_dir = os.getcwd()
os.chdir(path)
try:
yield
finally:
os.chdir(old_dir)
with change_dir("/tmp"):
# Working in /tmp
pass
# Back to original directoryThe contextlib Module
@contextmanager decorator
The easy way to create context managers:
from contextlib import contextmanager
@contextmanager
def timer():
start = time.time()
yield
elapsed = time.time() - start
print(f"Elapsed: {elapsed:.2f}s")
with timer():
do_something()
# Prints elapsed timesuppress exceptions
from contextlib import suppress
with suppress(FileNotFoundError):
os.remove("maybe_exists.txt")
# No error if file doesn't existredirect output
from contextlib import redirect_stdout
import io
buffer = io.StringIO()
with redirect_stdout(buffer):
print("Captured!")
output = buffer.getvalue() # "Captured!\n"nullcontext
from contextlib import nullcontext
# Useful for optional context managers
cm = open(file) if file else nullcontext()
with cm:
process()Multiple Context Managers
# Nested
with open("input.txt") as infile:
with open("output.txt", "w") as outfile:
outfile.write(infile.read())
# Same line (Python 3.10+)
with (
open("input.txt") as infile,
open("output.txt", "w") as outfile,
):
outfile.write(infile.read())Exception Handling
class Transaction:
def __enter__(self):
self.start_transaction()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None:
# Exception occurred - rollback
self.rollback()
else:
# Success - commit
self.commit()
return False # Don't suppress exceptions
with Transaction() as tx:
tx.execute("INSERT ...")
tx.execute("UPDATE ...")
# Auto-commits or rollbacksAsync Context Managers
class AsyncDB:
async def __aenter__(self):
self.conn = await connect()
return self
async def __aexit__(self, *args):
await self.conn.close()
async def main():
async with AsyncDB() as db:
await db.query("SELECT ...")Or with decorator:
from contextlib import asynccontextmanager
@asynccontextmanager
async def get_session():
session = await create_session()
try:
yield session
finally:
await session.close()My Patterns
- Use context managers for any resource that needs cleanup
- Prefer @contextmanager for simple cases
- Return useful values from
__enter__ - Don't suppress exceptions unless intentional
- Write async versions for async resources
Context managers make resource handling bulletproof. Use them everywhere cleanup matters.
React to this post: