Context Managers
Context managers are one of Python's most elegant features - they ensure resources
are properly acquired and released, even when errors occur. The with statement is the
Pythonic way to handle files, database connections, locks, and any resource that needs cleanup.
Understanding context managers will make your code safer, cleaner, and more professional!
The 'with' Statement
The with statement guarantees that cleanup code runs no matter what - even if an
exception occurs. For files, this means they're always closed properly. Compare it to try/finally:
the with statement is cleaner and less error-prone.
Click Run to execute your code
- Automatic cleanup (files closed, connections released)
- Exception-safe (cleanup runs even if errors occur)
- Cleaner code (no explicit try/finally blocks)
- Clear intent (setup and cleanup are paired together)
- Less bugs (can't forget to close resources)
Multiple Context Managers
You can use multiple context managers in a single with statement - perfect for
copying files, processing input to output, or managing multiple resources together. All resources
are cleaned up in reverse order of acquisition, ensuring proper shutdown.
Click Run to execute your code
with (
open("input.txt") as infile,
open("output.txt", "w") as outfile,
open("log.txt", "a") as logfile
):
# All three files available here
For older Python, use: with open("a") as f1, open("b") as f2:
Creating Custom Context Managers
Any class can become a context manager by implementing __enter__ and
__exit__ methods. The __enter__ method sets up the resource and returns
what gets bound to the as variable. The __exit__ method handles cleanup
and can optionally suppress exceptions.
Click Run to execute your code
__exit__ method receives
exception info as arguments. Return True to suppress the exception (usually not
recommended), or False to let it propagate. The cleanup code should run regardless!
The contextlib Module
Writing a class with __enter__ and __exit__ can be verbose. The
contextlib module provides shortcuts: the @contextmanager decorator
turns a generator function into a context manager, and utilities like suppress()
and closing() handle common patterns.
Click Run to execute your code
@contextmanager - Create context manager from generatorclosing(obj) - Call obj.close() on exitsuppress(*exceptions) - Ignore specified exceptionsredirect_stdout(f) - Redirect print() to filenullcontext() - Do-nothing context managerExitStack() - Manage dynamic number of context managers
Common Mistakes
1. Using file object after 'with' block
# Wrong - file is closed!
with open("data.txt", "r") as f:
pass
content = f.read() # ValueError: I/O on closed file
# Correct - read inside the block
with open("data.txt", "r") as f:
content = f.read()
# Or store what you need
with open("data.txt", "r") as f:
lines = f.readlines() # Now 'lines' persists
2. Forgetting 'as' when you need the resource
# Wrong - no way to access the file!
with open("data.txt", "r"):
content = ??? # Can't read without variable
# Correct - bind to variable
with open("data.txt", "r") as f:
content = f.read()
3. Not yielding in @contextmanager
from contextlib import contextmanager
# Wrong - forgot yield!
@contextmanager
def broken_manager():
print("Setup")
# Missing yield!
print("Cleanup")
# Correct - must yield
@contextmanager
def working_manager():
print("Setup")
yield # Control returns here
print("Cleanup")
4. Swallowing all exceptions in __exit__
# Wrong - hides all errors!
def __exit__(self, exc_type, exc_val, exc_tb):
self.cleanup()
return True # Suppresses ALL exceptions!
# Correct - only suppress specific exceptions
def __exit__(self, exc_type, exc_val, exc_tb):
self.cleanup()
if exc_type is SomeExpectedException:
return True # Only suppress this one
return False # Let others propagate
5. Cleanup not in finally block for @contextmanager
from contextlib import contextmanager
# Wrong - cleanup skipped on exception!
@contextmanager
def unsafe():
resource = acquire()
yield resource
release(resource) # Skipped if exception!
# Correct - use try/finally
@contextmanager
def safe():
resource = acquire()
try:
yield resource
finally:
release(resource) # Always runs
Exercise: Database Connection Manager
Task: Create a context manager that simulates database connection handling.
Requirements:
- Create a class-based context manager for database connections
- Print messages when connecting and disconnecting
- Include a query() method to simulate database queries
- Ensure the connection closes even if an error occurs
Click Run to execute your code
Show Solution
class DatabaseConnection:
"""Simulated database connection context manager."""
def __init__(self, database_name):
self.database_name = database_name
self.connected = False
def __enter__(self):
print(f"Connecting to database: {self.database_name}")
self.connected = True
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"Disconnecting from: {self.database_name}")
self.connected = False
if exc_type:
print(f"Error occurred: {exc_val}")
return False # Don't suppress exceptions
def query(self, sql):
if not self.connected:
raise RuntimeError("Not connected!")
print(f"Executing: {sql}")
return f"Results for: {sql}"
# Test normal operation
print("=== Normal Operation ===")
with DatabaseConnection("myapp.db") as db:
result = db.query("SELECT * FROM users")
print(result)
print()
# Test with exception
print("=== With Exception ===")
try:
with DatabaseConnection("myapp.db") as db:
db.query("SELECT * FROM users")
raise ValueError("Simulated error!")
except ValueError:
print("Exception caught outside context")
print()
# Verify connection is closed after exception
print(f"Connection state: {'connected' if db.connected else 'disconnected'}")
Summary
- with statement:
with open("f") as f:- auto cleanup - Multiple resources:
with open("a") as f1, open("b") as f2: - __enter__: Setup code, return value bound to
asvariable - __exit__: Cleanup code, receives exception info, return True to suppress
- @contextmanager: Create from generator with yield
- closing(): Call .close() automatically
- suppress(): Ignore specific exceptions
- Always cleanup: Use try/finally in @contextmanager
- Exception safety: Cleanup runs even if errors occur
- Best practice: Use context managers for all resources
What's Next?
Now that you understand context managers, let's explore working with paths using
Python's os.path and the modern pathlib module. These tools make it easy
to navigate directories, join paths, check file existence, and write cross-platform code!
Enjoying these tutorials?