Context Managers
Reviewed & published by Brayan K
Context managers are one of Python's most elegant and powerful tools — used everywhere from file handling, database transactions, network requests, locks, concurrency, and resource safety.
Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
- • What a context manager is and how with works under the hood
- • Creating class-based context managers with __enter__ and __exit__
- • Using @contextmanager from contextlib for simpler patterns
- • Handling errors inside context managers gracefully
- • Real-world uses: database sessions, file I/O, timers, locks
What You'll Learn
If you've ever written with open("file.txt") as f: …you've already used a context manager.
This lesson teaches you:
✔ How context managers work
✔ How to create your own using classes
✔ How to create them using generator functions (contextlib.contextmanager)
✔ How real systems use them for safe resource handling
✔ How to build production-grade context managers
✔ How to combine context managers with decorators & advanced design patterns
🔥 1. What Is a Context Manager?
A context manager controls a setup phase and a cleanup phase.
| Phase | What Happens | Hotel Analogy |
|---|---|---|
| __enter__ | Resource is acquired/prepared | Check in, get room key |
| with block | Your code runs using the resource | Enjoy your stay |
| __exit__ | Resource is cleaned up/released | Checkout, room cleaned |
with something as value:
# do workThe moment execution enters the with block, the context manager prepares a resource. When the block exits — even if an error occurs — the resource is cleaned up.
- Network connections
- Database sessions
- Locks (threading, multiprocessing)
- Temporary directories
- Mocking in unit tests
⚙️ 2. How Python Processes the with Statement
with manager as x:
work()manager_obj = manager.__enter__()
try:
x = manager_obj
work()
finally:
manager.__exit__(*sys.exc_info())So a context manager must define:
- Runs when entering the block
- Returns an optional value
✔ __exit__(self, exc_type, exc, tb)
- Runs when leaving
- Cleans up resources
- Can suppress errors by returning True
🧠 3. Creating Your Own Context Manager (Class-Based)
Let's build a simple context manager that logs entering/exiting:
class Logger:
def __enter__(self):
print("Entering block...")
return "Ready!"
def __exit__(self, exc_type, exc, tb):
print("Exiting block...")
if exc:
print("Error occurred:", exc)
return False # do not suppress errorswith Logger() as msg:
print(msg)Entering block...
Ready!
Exiting block...🧩 4. A Real Project Example — Timing Block Execution
import time
class Timer:
def __enter__(self):
self.start = time.time()
return self
def __exit__(self, exc_type, exc, tb):
self.end = time.time()
print(f"⏱ Elapsed: {self.end - self.start:.4f}s")with Timer():
sum([x for x in range(5000000)])🧨 5. A More Advanced Example — Database Connections
This pattern exists in ORMs like SQLAlchemy & Django:
class Transaction:
def __enter__(self):
print("Start transaction")
return self
def __exit__(self, exc_type, exc, tb):
if exc_type:
print("Rollback")
else:
print("Commit")with Transaction():
print("Updating user...")🧊 6. Using contextlib.contextmanager (Generator Context Managers)
For simpler cases, Python provides a shortcut.
from contextlib import contextmanager
@contextmanager
def open_file(path):
f = open(path)
try:
yield f # __enter__()
finally:
f.close() # __exit__()with open_file("data.txt") as file:
print(file.read())This is extremely common in:
- testing tools
- temporary environment changes
- locking functions
- resource wrappers
🔧 7. Building a Temporary Directory Context Manager
import tempfile
import shutil
from contextlib import contextmanager
@contextmanager
def tempdir():
path = tempfile.mkdtemp()
try:
yield path
finally:
shutil.rmtree(path)with tempdir() as path:
print("Working in", path)🔐 8. Context Managers for Locking & Thread Safety
Example using threading lock:
from threading import Lock
lock = Lock()
with lock:
print("Critical section")
# ✅ Expected output:
# Critical sectionLocks are native context managers. This pattern protects shared memory in concurrent programs.
🧬 9. Context Managers Used With Decorators (Advanced Pattern)
You can combine context managers with decorators to automatically wrap function execution:
from contextlib import contextmanager
from functools import wraps
import time
@contextmanager
def timer_block():
start = time.time()
yield
print(f"Elapsed: {time.time() - start:.4f}s")
def timed(fn):
@wraps(fn)
def wrapper(*a, **k):
with timer_block():
return fn(*a, **k)
return wrapper
@timed
def process():
time.sleep(0.2)process()🧪 10. Suppressing Errors (But Carefully!)
If __exit__ returns True, exceptions are swallowed.
class IgnoreErrors:
def __exit__(self, exc_type, exc, tb):
return Truewith IgnoreErrors():
1 / 0 # error suppressedBe careful — this is useful ONLY for:
- cleanup tasks
- safe shutdown
Never suppress runtime errors silently in real systems.
📚 11. Nested Context Managers
with A() as a:
with B() as b:
...with A() as a, B() as b:
...This is cleaner and avoids deep nesting.
⚡ 12. Combining Multiple Context Managers Dynamically
Useful for frameworks or plugin systems:
from contextlib import ExitStack
with ExitStack() as stack:
files = [stack.enter_context(open(f)) for f in file_list]
# all files now managed safelyThis is extremely powerful for:
- ML pipelines
- batch processing
- plugin environments
🏗 13. Context Managers in Real Frameworks
async def db():
async with engine.begin() as conn:
yield conn✔ PyTorch CUDA device switching
with torch.no_grad():
...with transaction.atomic():
...with np.printoptions(suppress=True):
...Context managers are everywhere.
🧱 14. Build a Production-Ready Context Manager
Let's create a file-write-safe manager:
class SafeWriter:
def __init__(self, path):
self.path = path
def __enter__(self):
self.temp = open(self.path + ".tmp", "w")
return self.temp
def __exit__(self, exc_type, exc, tb):
self.temp.close()
if exc_type:
# remove corrupted temp file
import os
os.remove(self.path + ".tmp")
return False
import os
os.replace(self.path + ".tmp", self.path)- No half-written files
- Atomic writes
- OS-level safety
This is similar to how:
- Config managers
- Database migrations
🎓 15. Mini Project — Build Your Own Resource Manager
Create a context manager that:
- ✔ tracks memory before/after
- ✔ logs execution time
- ✔ logs exceptions
- ✔ sends results to a log file
- ✔ optionally suppresses errors
with Monitor("log.txt", suppress=False):
run_expensive_task()# 🎯 YOUR TURN — replace each ___ using the hint beside it.
from contextlib import contextmanager
class Section:
def __init__(self, title):
self.title = title
# 1) Runs on the way in. Whatever it RETURNS is whatThis gives you portfolio-ready experience.
🎉 Conclusion
You now understand context managers:
✔ How the with statement works internally
✔ How to build class-based managers
✔ How to build generator-based managers
✔ How to combine decorators and context managers
✔ How to suppress or inspect exceptions
✔ How they power major frameworks
✔ How to create production-grade resource managers
📋 Quick Reference — Context Managers
| Syntax | What it does |
|---|---|
| with open(f) as fh: | Open file safely, auto-closes on exit |
| __enter__(self) | Called when entering the with block |
| __exit__(self, *args) | Called on exit, even if an error occurs |
| @contextmanager | Build a manager from a generator function |
| contextlib.suppress(E) | Ignore a specific exception type |
🏆 Lesson Complete!
You can now build and use context managers for any resource lifecycle — files, databases, locks, timers, and more.
Practice quiz
Which two phases does a context manager control?
- Compile and run
- Read and write
- A setup phase and a cleanup phase
- Import and export
Answer: A setup phase and a cleanup phase. A context manager manages a setup phase (on entry) and a cleanup phase (on exit).
Which two methods must a class-based context manager define?
- __enter__ and __exit__
- __init__ and __del__
- __aenter__ and __aexit__
- open and close
Answer: __enter__ and __exit__. Class-based context managers implement __enter__ (entry) and __exit__ (cleanup).
The value returned by __enter__ becomes what?
- The exception type
- The return value of the whole block
- Always None
- The variable bound by 'as' in the with statement
Answer: The variable bound by 'as' in the with statement. __enter__'s return value is assigned to the 'as' variable in the with statement.
Does __exit__ run if an exception is raised inside the with block?
- No, exceptions skip cleanup
- Yes, __exit__ always runs (it's like a finally)
- Only if you catch the exception first
- Only for KeyboardInterrupt
Answer: Yes, __exit__ always runs (it's like a finally). __exit__ runs whether the block finishes normally or raises — like a finally clause.
How does __exit__ suppress (swallow) an exception that occurred in the block?
- By returning True
- By raising it again
- By returning None
- By calling sys.exit()
Answer: By returning True. Returning True from __exit__ tells Python to suppress the exception.
In a @contextmanager generator, where does the cleanup code go?
- Before the yield
- In a separate function
- After the yield
- In __init__
Answer: After the yield. Setup runs before yield; the cleanup code runs after the yield.
How many times must a @contextmanager generator yield?
- Zero times
- Exactly once
- Twice
- As many as you like
Answer: Exactly once. It must yield exactly once; zero or multiple yields raise a RuntimeError.
What is the modern way to use two context managers in a single with statement?
- with A() as a: with B() as b:
- with A() and B():
- with [A(), B()]:
- with A() as a, B() as b:
Answer: with A() as a, B() as b:. with A() as a, B() as b: manages both on one line, avoiding deep nesting.
What does contextlib.ExitStack let you do?
- Run code faster
- Manage a dynamic, runtime-determined number of context managers
- Suppress all exceptions
- Replace try/except
Answer: Manage a dynamic, runtime-determined number of context managers. ExitStack enters a variable number of context managers and exits them all (in reverse) at block end.
Are threading locks usable directly as context managers with 'with lock:'?
- No, you must wrap them
- Only async locks support it
- Yes, locks are native context managers
- Only with ExitStack
Answer: Yes, locks are native context managers. Locks implement the context manager protocol, so 'with lock:' acquires and releases automatically.
Continue this course
- Previous: Closures & Lexical Scope in Real Projects
- Next: Generators & Iterators Mastery — Produce values lazily for memory-efficient data pipelines
- Quick reference: Python cheat sheet
- From the blog: Error Handling Best Practices in Python