Context Managers Explained¶
A context manager is something that sets up a resource when you enter a block and cleans it up when you leave — even if an error occurs. The with statement is how you use them. If you have ever written with open("file.txt") as f:, you have already used one.
Learn Your Way¶
| Read | Build | Watch | Test | Review | Visualize |
|---|---|---|---|---|---|
| You are here | Projects | Videos | Quiz | Flashcards | Diagrams |
Why This Matters¶
Resources like files, database connections, and network sockets need to be properly closed when you are done with them. Forgetting to close a file can corrupt data. Forgetting to close a database connection can exhaust the connection pool and crash your server. Context managers make cleanup automatic — you cannot forget.
Visualize It¶
Watch how with guarantees cleanup, even when an error is raised:
Open in Python Tutor
The basic pattern — with and files¶
Without a context manager, you must remember to close the file yourself:
# WITHOUT a context manager — risky
f = open("data.txt")
content = f.read()
f.close() # What if an error happens before this line?
With a context manager, cleanup is guaranteed:
# WITH a context manager — safe
with open("data.txt") as f:
content = f.read()
# f.close() is called automatically, even if an error occurred
The with statement calls f.__enter__() at the start and f.__exit__() at the end, no matter what happens inside the block.
How __enter__ and __exit__ work¶
Any object can be a context manager if it has two special methods:
class ManagedFile:
def __init__(self, filename):
self.filename = filename
def __enter__(self):
# Called when entering the `with` block
self.file = open(self.filename)
return self.file # This becomes the `as` variable
def __exit__(self, exc_type, exc_val, exc_tb):
# Called when leaving the `with` block — ALWAYS
self.file.close()
return False # False means: do not suppress exceptions
# Usage:
with ManagedFile("data.txt") as f:
content = f.read()
The three arguments to __exit__ describe any exception that occurred:
- exc_type — the exception class (e.g., ValueError), or None if no error
- exc_val — the exception instance
- exc_tb — the traceback object
If __exit__ returns True, the exception is suppressed (swallowed). If it returns False (the default), the exception propagates normally. Almost always return False.
The easy way — contextlib.contextmanager¶
Writing a class with __enter__ and __exit__ is verbose. The contextlib module gives you a decorator that turns a generator function into a context manager:
from contextlib import contextmanager
@contextmanager
def managed_file(filename):
# __enter__: everything before yield
f = open(filename)
try:
yield f # This value becomes the `as` variable
finally:
# __exit__: everything after yield
f.close()
# Usage — exactly the same:
with managed_file("data.txt") as f:
content = f.read()
The pattern is:
1. Before yield — setup (like __enter__)
2. yield value — the value assigned by as
3. After yield — cleanup (like __exit__), usually in a finally block
Real-world examples¶
Database connection¶
from contextlib import contextmanager
import sqlite3
@contextmanager
def get_db(path):
conn = sqlite3.connect(path)
try:
yield conn
conn.commit() # Commit if no errors
except Exception:
conn.rollback() # Rollback on error
raise # Re-raise the exception
finally:
conn.close() # Always close
with get_db("app.db") as conn:
conn.execute("INSERT INTO users (name) VALUES (?)", ("Alice",))
# If this block raises, the transaction is rolled back
# If it succeeds, changes are committed
# Either way, the connection is closed
Temporary directory¶
import tempfile
import os
# Python's built-in context manager for temp directories
with tempfile.TemporaryDirectory() as tmpdir:
path = os.path.join(tmpdir, "temp_file.txt")
with open(path, "w") as f:
f.write("temporary data")
# Work with the directory...
# Directory and all contents are deleted automatically
Timing a block of code¶
from contextlib import contextmanager
import time
@contextmanager
def timer(label):
start = time.time()
yield
elapsed = time.time() - start
print(f"{label}: {elapsed:.2f}s")
with timer("data processing"):
# ... expensive work here ...
total = sum(range(10_000_000))
# Prints: data processing: 0.23s
Suppressing specific exceptions¶
from contextlib import suppress
# Instead of try/except/pass:
with suppress(FileNotFoundError):
os.remove("temp.txt")
# If the file does not exist, no error — the exception is silently caught
Thread lock¶
import threading
lock = threading.Lock()
# The lock is a context manager — it acquires on enter, releases on exit
with lock:
# Only one thread can be here at a time
shared_data.append(item)
Nesting context managers¶
You can nest with statements, or combine them on one line:
# Nested:
with open("input.txt") as src:
with open("output.txt", "w") as dst:
dst.write(src.read())
# Combined (Python 3.1+):
with open("input.txt") as src, open("output.txt", "w") as dst:
dst.write(src.read())
# Parenthesized (Python 3.10+):
with (
open("input.txt") as src,
open("output.txt", "w") as dst,
):
dst.write(src.read())
Common Mistakes¶
Forgetting that __exit__ always runs:
# This is fine — __exit__ runs even if you return early
with open("data.txt") as f:
first_line = f.readline()
if not first_line:
return # File is still closed properly
Suppressing exceptions by accident:
class BadContextManager:
def __exit__(self, exc_type, exc_val, exc_tb):
self.cleanup()
return True # DANGER: swallows ALL exceptions silently!
Always return False from __exit__ unless you have a very specific reason to suppress exceptions.
Using a closed resource outside the with block:
The resource is only valid inside the with block.
Practice¶
- Module 06 Databases & ORM — database connections with context managers
- Level 1 / 01 Text Stats — file reading with
with - Level 1 / 05 CSV First Reader — CSV files with
with
Further Reading¶
- contextlib — Utilities for with-statement contexts
- The with statement (Python docs)
- PEP 343 — The "with" Statement
| ← Prev | Home | Next → |
|---|---|---|