Context Managers & 'with'
Learn to use context managers and the 'with' statement in Python for resource management, with hands-on exercises and troubleshooting.
Focus: work with context managers and the 'with' statement
You've written code that opens files, locks threads, or connects to databases, but you're tired of manually closing them — and one forgotten f.close() can corrupt data or leak memory. That's the exact pain context managers solve: they automatically set up and tear down resources, no matter what. By the end of this lesson, you'll confidently write clean, safe Python with the with statement and even build your own context managers.
The problem this lesson solves
Imagine you're reading a large log file. Without a context manager, you must remember to close the file every time:
f = open('server.log', 'r')
data = f.read()
f.close() # easy to forget or skip in an exception
If an exception occurs before f.close(), the file stays open. Open files consume system handles, and on Windows you can't delete or move them. Over time, leaked handles crash applications. Context managers eliminate this headache by guaranteeing cleanup.
Core concept / mental model
A context manager is an object that defines two magic methods: __enter__ and __exit__. The with statement calls __enter__ at the start and __exit__ at the end, even if an error happens. Think of it like a hotel key: you enter the room, get the key (__enter__), and when you leave, you exit and return the key (__exit__) — no matter why you left.
Pro tip: Every resource that needs setup/teardown (files, locks, database connections, network sockets) should be wrapped with
with.
How it works step by step
- Python evaluates the expression after
with— this must return a context manager. - The context manager's
__enter__method runs — its return value is assigned to theasvariable (if used). - The indented block executes — your main code runs here.
- When the block finishes (or an exception occurs),
__exit__is called — it receives exception info orNoneif success. - If
__exit__returnsTrue, the exception is suppressed (rarely done).
Here's the same file-reading example with with:
with open('server.log', 'r') as f:
data = f.read()
# f is already closed here, even if an exception occurred
The file is closed before the next line of code. No manual cleanup needed.
Hands-on walkthrough
Basic file reading
# Write a sample file first
with open('example.txt', 'w') as f:
f.write('Hello, context managers!')
# Read it back
with open('example.txt', 'r') as f:
content = f.read()
print(content) # Output: Hello, context managers!
# Confirm file is closed: accessing f.name works, but I/O fails
# print(f.read()) # Uncommenting raises: ValueError: I/O operation on closed file.
Using multiple context managers
You can nest with statements or use a single with with commas (Python 3.1+):
# Nested (explicit)
with open('source.txt', 'r') as src:
with open('dest.txt', 'w') as dst:
dst.write(src.read())
# Single line (cleaner for multiple resources)
with open('source.txt', 'r') as src, open('dest.txt', 'w') as dst:
dst.write(src.read())
Both forms ensure all resources are cleaned up. The second option is more readable.
Writing a custom context manager using a class
When you need to manage something other than files, create your own:
class ManagedResource:
def __enter__(self):
print('Acquiring resource')
return self # This becomes the 'as' variable
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None:
print(f'Exception {exc_type.__name__}: {exc_val}')
print('Releasing resource')
return False # Propagate the exception (default behavior)
with ManagedResource() as res:
print('Using resource')
# raise ValueError('Oops') # Uncomment to see cleanup still runs
Output:
Acquiring resource
Using resource
Releasing resource
If an exception occurs, __exit__ still runs.
Using contextlib for simpler one-time managers
The contextlib module provides @contextmanager for generators (Python 3.6+):
from contextlib import contextmanager
@contextmanager
def managed_block(label):
print(f'Entering {label}')
yield
print(f'Exiting {label}')
with managed_block('test'):
print('Inside block')
Output:
Entering test
Inside block
Exiting test
The yield splits __enter__ (before yield) from __exit__ (after yield). Wrap the yield in a try/finally to handle errors inside the block.
Compare options / when to choose what
| Approach | Use case | Example |
|---|---|---|
with + built-in (file, lock) |
Standard resources | with open(...) as f: |
Custom class with __enter__/__exit__ |
Complex setup/teardown | Database connection, network socket |
@contextmanager decorator |
Simple manager without a class | Quick resource wrapper |
contextlib.closing() |
Legacy objects with .close() |
with closing(urllib.urlopen(...)): (rare) |
When to choose:
- For files, locks, and threading primitives, use with directly.
- For custom resources (e.g., a timer, a database cursor), write a class or use @contextmanager.
- For temporary files/directories, use tempfile.TemporaryFile() or pathlib with with.
Troubleshooting & edge cases
- File not closed when
withexits explicitly: If you callbreak,return, orcontinueinside the block,__exit__still runs. That's by design. - Multiple context managers comma-separated: Order matters — each
__exit__is called in reverse order. - Custom manager doesn't handle exceptions: If
__exit__returnsFalse(orNone), the exception propagates. To suppress, returnTrue. Use sparingly — it hides bugs. __exit__signature wrong: Must accept exactly (self, exc_type, exc_val, exc_tb). Missing args causeTypeError.- Generator-based context manger throws
RuntimeError: If youyieldmore than once, Python raises an error. Always useyieldexactly once.
Common error message:
AttributeError: __enter__
This means the object after with is not a context manager. Check that you're using a proper context manager (e.g., open() returns one).
What you learned & what's next
You now understand how with statements guarantee resource cleanup, how to use them with files, how to write custom class-based and generator-based context managers, and the key differences between approaches. You've mastered one of Python's most elegant resource management patterns.
Next step: Learn how context managers integrate with Python's exception handling in depth, or explore advanced contextlib utilities like ExitStack for dynamic context management.
Practice recap
Try this: Write a custom context manager that counts how many lines were read from a file. Use a class-based approach where __enter__ opens the file and __exit__ prints the line count. Then test it by reading a small file of your own — you'll see the power of automatic finalization.
Common mistakes
- Forgetting to wrap custom resources in
with— leaving files or locks open when exceptions occur. - Using a non-context-manager object with
with, resulting inAttributeError: __enter__. - Defining
__exit__with wrong number of parameters — must accept (self, exc_type, exc_val, exc_tb). - Returning
Truefrom__exit__without understanding that it suppresses all exceptions, hiding bugs.
Variations
- Use
contextlib.closing()for objects with a.close()method but no__enter__/__exit__. - Use
contextlib.redirect_stdout()to temporarily redirect standard output inside awithblock. - Use
contextlib.ExitStackwhen you need to manage a dynamic set of context managers at runtime.
Real-world use cases
- Open a file for reading, process lines, and guarantee the file handle is released even on parse errors.
- Wrap a shared database connection pool: acquire a connection on enter, return it on exit.
- Automate temporary directory creation (with
tempfile.TemporaryDirectory()) for unit tests, ensuring cleanup on failure.
Key takeaways
- The
withstatement calls__enter__on start and__exit__on end, ensuring cleanup regardless of how the block exits. - Built-in objects like file handles, locks (from threading), and
decimal.localcontext()are ready-to-use context managers. - Write a custom context manager by implementing
__enter__and__exit__(class) or using the@contextmanagerdecorator with a generator. - When using multiple
withmanagers, comma-separate them:with open('a', 'r') as f1, open('b', 'w') as f2:. - The
contextlibmodule provides tools likeclosing()andExitStackfor advanced scenarios. - Always return
False(orNone) from__exit__to propagate exceptions — suppress only when you have a good reason.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.