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, so 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 → |
|---|---|---|