- What are context managers ā a way to manage resources automatically
- Why use them ā clean, safe, and automatic resource management
- The with statement ā how to use context managers
- Built-in context managers ā files, locks, and more
- Creating your own ā two ways to build context managers
- Real-world use ā practical examples
What are Context Managers?
A context manager is a Python object that helps you manage resources like files, network connections, or database connections. It makes sure that resources are properly set up before you use them and properly cleaned up after you're done.
Think of it like a restaurant manager. When you sit at a table, the manager makes sure the table is clean and ready for you. When you leave, the manager makes sure the table is cleaned up for the next person. You don't have to worry about it.
In Python, context managers work with the with statement. You've probably used them before without realizing it.
š” Key concept: Context managers automatically handle setup and cleanup. They make sure resources are properly released even if something goes wrong.
Why Use Context Managers?
The Benefits of Context Managers
Context managers make your code safer and cleaner. Let's see why.
# Why Use Context Managers?
print("=" * 50)
print("WHY USE CONTEXT MANAGERS?")
print("=" * 50)
# ============================================================
# WITHOUT CONTEXT MANAGER ā Manual and Error-Prone
# ============================================================
print("\nā WITHOUT CONTEXT MANAGER:")
file = None
try:
file = open("example.txt", "w")
file.write("Hello, World!")
file.close() # Manual cleanup
except Exception as e:
print(f" Error: {e}")
finally:
if file and not file.closed:
file.close() # Need to handle this too
print(" ā Lots of code just to handle a file")
print(" ā Easy to forget to close the file")
print(" ā Harder to read and maintain")
# ============================================================
# WITH CONTEXT MANAGER ā Clean and Automatic
# ============================================================
print("\nā
WITH CONTEXT MANAGER:")
with open("example.txt", "w") as file:
file.write("Hello, World!")
# File is automatically closed when the block ends
print(" ā
Just 2 lines of code")
print(" ā
File is automatically closed")
print(" ā
Even if an error happens, the file is closed")
print(" ā
Much cleaner and safer")
# ============================================================
# BENEFITS
# ============================================================
print("\n" + "-" * 30)
print("BENEFITS OF CONTEXT MANAGERS")
print("-" * 30)
print("""
āāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā BENEFIT ā WHAT IT MEANS FOR YOU ā
āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Automatic cleanup ā Resources are always properly released ā
ā ā ā
ā Exception safety ā Even if errors happen, cleanup happens ā
ā ā ā
ā Less code ā No more try/finally blocks ā
ā ā ā
ā Readable code ā The with statement clearly shows ā
ā ā the scope of the resource ā
ā ā ā
ā Consistent behavior ā Same pattern for all resources ā
āāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
š Context managers make resource management automatic and safe.
""")
Benefits of context managers:
- Automatic cleanup ā resources are always released
- Exception safety ā cleanup happens even with errors
- Less code ā no manual try/finally blocks
- Readable ā the
withstatement makes the scope clear - Consistent ā same pattern for all resources
Quick Check: What does a context manager handle automatically? (Answer: Setup and cleanup of resources)
The with Statement
How to Use the with Statement
The with statement is how you use a context manager. It's simple and easy to remember.
# The with Statement
print("=" * 50)
print("THE WITH STATEMENT")
print("=" * 50)
# ============================================================
# BASIC USAGE
# ============================================================
print("\n1. BASIC USAGE")
# Open a file and automatically close it
with open("test.txt", "w") as file:
file.write("Hello, World!")
print(" File written and closed automatically")
# Reading from a file
with open("test.txt", "r") as file:
content = file.read()
print(f" File content: {content}")
# ============================================================
# MULTIPLE CONTEXT MANAGERS
# ============================================================
print("\n2. MULTIPLE CONTEXT MANAGERS")
# Copy from one file to another
with open("test.txt", "r") as source, open("copy.txt", "w") as dest:
content = source.read()
dest.write(content)
print(" File copied successfully")
# Or on separate lines
with open("test.txt", "r") as source:
with open("copy.txt", "w") as dest:
content = source.read()
dest.write(content)
print(" File copied (nested)")
# ============================================================
# CONTEXT MANAGER WITH ASYNCHRONOUS CODE
# ============================================================
print("\n3. CONTEXT MANAGER WITH ASYNCIO (Async)")
import asyncio
async def read_file_async():
# Async context manager (Python 3.7+)
async with open("test.txt", "r") as file:
content = file.read()
return content
# async def run():
# content = await read_file_async()
# print(f" Async read: {content}")
#
# asyncio.run(run())
print(" ā
async with works with async context managers")
# ============================================================
# NAMING THE CONTEXT MANAGER
# ============================================================
print("\n4. NAMING THE CONTEXT MANAGER")
# The variable after 'as' is the context manager's result
with open("test.txt", "r") as file: # 'file' is the file object
content = file.read()
print(f" Content: {content}")
# You can also use it without 'as'
with open("test.txt", "r"): # Less common
print(" File is open, but we don't have a reference")
# ============================================================
# WHAT HAPPENS BEHIND THE SCENES
# ============================================================
print("\n5. WHAT HAPPENS BEHIND THE SCENES")
print("""
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā The with statement does this: ā
ā ā
ā 1. Calls the context manager's __enter__() method ā
ā 2. Executes the code inside the block ā
ā 3. Calls the context manager's __exit__() method ā
ā - Even if an exception happens! ā
ā ā
ā This is why resources are always properly cleaned up. ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
""")
The with statement key points:
- with ... as ā the syntax for using context managers
- Multiple managers ā can use multiple in one line
- Async with ā
async withfor async context managers - Automatic ā cleanup happens even with errors
- Scope ā the resource is only available inside the block
Quick Check: What does the 'as' keyword do in a with statement? (Answer: It gives you a reference to the context manager's result)
Built-in Context Managers
Context Managers Already in Python
Python has many built-in context managers that you can use right away.
# Built-in Context Managers
print("=" * 50)
print("BUILT-IN CONTEXT MANAGERS")
print("=" * 50)
import threading
import tempfile
import contextlib
# ============================================================
# 1. FILE OPERATIONS
# ============================================================
print("\n1. FILE OPERATIONS")
# Reading a file
with open("data.txt", "w") as f:
f.write("Hello, World!")
with open("data.txt", "r") as f:
content = f.read()
print(f" Content: {content}")
# ============================================================
# 2. THREAD LOCKS
# ============================================================
print("\n2. THREAD LOCKS")
lock = threading.Lock()
def safe_function():
with lock:
# Only one thread can run this at a time
print(" Thread-safe operation")
# Lock is automatically released
safe_function()
# ============================================================
# 3. TEMPORARY FILES
# ============================================================
print("\n3. TEMPORARY FILES")
with tempfile.NamedTemporaryFile(mode='w', delete=True) as temp:
temp.write("Temporary data")
temp.flush()
print(f" Temporary file: {temp.name}")
# File is automatically deleted when the block ends
# ============================================================
# 4. SUPPRESSING ERRORS (contextlib)
# ============================================================
print("\n4. SUPPRESSING ERRORS")
from contextlib import suppress
with suppress(FileNotFoundError):
# This won't raise an error if the file doesn't exist
with open("missing.txt", "r") as f:
content = f.read()
print(" ā No error raised (FileNotFoundError was suppressed)")
# ============================================================
# 5. CHANGING DIRECTORY (contextlib)
# ============================================================
print("\n5. CHANGING DIRECTORY")
import os
print(f" Current directory: {os.getcwd()}")
with contextlib.chdir(".."): # Go up one directory
print(f" In with block: {os.getcwd()}")
# Directory is restored after the block
print(f" Back to original: {os.getcwd()}")
# ============================================================
# 6. REDIRECTING OUTPUT (contextlib)
# ============================================================
print("\n6. REDIRECTING OUTPUT")
import io
with io.StringIO() as buffer:
with contextlib.redirect_stdout(buffer):
print("This goes to the buffer, not the console")
output = buffer.getvalue()
print(f" Captured output: {repr(output)}")
# ============================================================
# 7. TIMING CODE
# ============================================================
print("\n7. TIMING CODE")
import time
class Timer:
def __enter__(self):
self.start = time.time()
return self
def __exit__(self, *args):
self.end = time.time()
print(f" Time taken: {self.end - self.start:.4f} seconds")
with Timer():
time.sleep(0.5)
print(" Operation completed")
# ============================================================
# SUMMARY
# ============================================================
print("\n" + "-" * 30)
print("BUILT-IN CONTEXT MANAGERS SUMMARY")
print("-" * 30)
print("""
āāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā CONTEXT MANAGER ā WHAT IT DOES ā
āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā open() ā Opens and automatically closes files ā
ā threading.Lock ā Acquires and releases locks ā
ā tempfile ā Creates and deletes temporary files ā
ā suppress() ā Suppresses specified exceptions ā
ā chdir() ā Changes directory and restores it ā
ā redirect_stdout() ā Redirects output temporarily ā
āāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
š Python has many built-in context managers. Learn them and use them!
""")
Built-in context managers key points:
- open() ā file operations
- threading.Lock ā thread safety
- tempfile ā temporary files
- suppress() ā ignore specific errors
- redirect_stdout() ā capture output
Quick Check: What context manager would you use to ignore a FileNotFoundError? (Answer: contextlib.suppress(FileNotFoundError))
Creating Context Managers (Class)
The __enter__ and __exit__ Methods
You can create your own context manager by defining a class with __enter__ and __exit__ methods.
# Creating Context Managers with Classes
print("=" * 50)
print("CREATING CONTEXT MANAGERS (CLASS)")
print("=" * 50)
import time
# ============================================================
# BASIC CONTEXT MANAGER
# ============================================================
print("\n1. BASIC CONTEXT MANAGER")
class FileOpener:
"""A context manager for opening files"""
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
"""Called when entering the with block"""
print(f" Opening file: {self.filename}")
self.file = open(self.filename, self.mode)
return self.file # This is what 'as' gets
def __exit__(self, exc_type, exc_val, exc_tb):
"""Called when exiting the with block"""
print(f" Closing file: {self.filename}")
if self.file:
self.file.close()
# Return False to propagate exceptions, True to suppress them
return False
# Using the context manager
with FileOpener("test.txt", "w") as file:
file.write("Hello, World!")
# ============================================================
# TIMING CONTEXT MANAGER
# ============================================================
print("\n2. TIMING CONTEXT MANAGER")
class TimerContext:
"""Context manager that measures execution time"""
def __enter__(self):
self.start = time.perf_counter()
print(" Timer started")
return self
def __exit__(self, *args):
self.end = time.perf_counter()
self.duration = self.end - self.start
print(f" Timer stopped: {self.duration:.4f}s")
return False
with TimerContext() as timer:
time.sleep(0.5)
print(" Doing some work...")
# ============================================================
# CONTEXT MANAGER WITH EXCEPTION HANDLING
# ============================================================
print("\n3. CONTEXT MANAGER WITH EXCEPTION HANDLING")
class SafeDivider:
"""Context manager that handles division by zero"""
def __enter__(self):
print(" Entering division context")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is ZeroDivisionError:
print(f" Caught division by zero: {exc_val}")
return True # Suppress the exception
return False # Let other exceptions propagate
def divide(self, a, b):
return a / b
with SafeDivider() as divider:
# This would normally raise an error
result = divider.divide(10, 0)
print(f" Result: {result}")
print(" Program continues normally")
# ============================================================
# CONTEXT MANAGER WITH RESOURCE MANAGEMENT
# ============================================================
print("\n4. CONTEXT MANAGER WITH RESOURCE MANAGEMENT")
class DatabaseConnection:
"""Simulate a database connection context manager"""
def __init__(self, db_name):
self.db_name = db_name
self.connected = False
def __enter__(self):
print(f" Connecting to {self.db_name}")
self.connected = True
return self
def __exit__(self, *args):
print(f" Disconnecting from {self.db_name}")
self.connected = False
return False
def query(self, sql):
if not self.connected:
raise RuntimeError("Not connected to database")
return f" Result of: {sql}"
with DatabaseConnection("users.db") as db:
result = db.query("SELECT * FROM users")
print(result)
print(" Database connection closed")
# ============================================================
# UNDERSTANDING __exit__ PARAMETERS
# ============================================================
print("\n5. UNDERSTANDING __exit__ PARAMETERS")
class ExceptionHandler:
"""Demonstrates the __exit__ parameters"""
def __enter__(self):
print(" Entering context")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(f" exc_type: {exc_type}")
print(f" exc_val: {exc_val}")
print(f" exc_tb: {exc_tb}")
return False
print(" No exception:")
with ExceptionHandler():
pass
print("\n With exception:")
try:
with ExceptionHandler():
raise ValueError("Something went wrong!")
except ValueError as e:
print(f" Caught exception: {e}")
Class-based context managers key points:
- __enter__ ā called when entering the
withblock - __exit__ ā called when exiting, even with errors
- Return value ā __enter__ returns the resource
- Exception handling ā __exit__ gets exception details
- Suppress exceptions ā return True from __exit__
Quick Check: What two methods does a class need to be a context manager? (Answer: __enter__ and __exit__)
Creating Context Managers (contextlib)
The Easier Way with contextlib
The contextlib module gives you a simpler way to create context managers using the @contextmanager decorator.
# Creating Context Managers with contextlib
print("=" * 50)
print("CREATING CONTEXT MANAGERS (contextlib)")
print("=" * 50)
from contextlib import contextmanager
import time
# ============================================================
# BASIC CONTEXT MANAGER
# ============================================================
print("\n1. BASIC CONTEXT MANAGER")
@contextmanager
def open_file(filename, mode):
"""Context manager for files (using contextlib)"""
print(f" Opening file: {filename}")
file = open(filename, mode)
try:
yield file # This is the resource
finally:
print(f" Closing file: {filename}")
file.close()
with open_file("test.txt", "w") as file:
file.write("Hello, World!")
# ============================================================
# TIMING CONTEXT MANAGER
# ============================================================
print("\n2. TIMING CONTEXT MANAGER")
@contextmanager
def timer(name="Operation"):
"""Context manager that measures execution time"""
print(f" ā±ļø Starting {name}...")
start = time.perf_counter()
try:
yield
finally:
duration = time.perf_counter() - start
print(f" ā±ļø {name} took {duration:.4f}s")
with timer("Sleep"):
time.sleep(0.5)
print(" Work done")
# ============================================================
# CONTEXT MANAGER WITH RESOURCES
# ============================================================
print("\n3. CONTEXT MANAGER WITH RESOURCES")
@contextmanager
def database_connection(db_name):
"""Simulate a database connection"""
print(f" Connecting to {db_name}")
# Setup
connection = {"connected": True, "db": db_name}
try:
yield connection # Resource
finally:
print(f" Disconnecting from {db_name}")
connection["connected"] = False
with database_connection("users.db") as db:
print(f" Connected: {db['connected']}")
print(f" Database: {db['db']}")
# ============================================================
# CONTEXT MANAGER WITH EXCEPTION HANDLING
# ============================================================
print("\n4. CONTEXT MANAGER WITH EXCEPTION HANDLING")
@contextmanager
def ignore_errors(error_types):
"""Context manager that ignores specified errors"""
try:
yield
except error_types as e:
print(f" Ignored error: {e}")
with ignore_errors(ValueError):
print(" Trying something that might fail")
raise ValueError("This error will be ignored!")
print(" Program continues normally")
# ============================================================
# NESTED CONTEXT MANAGERS
# ============================================================
print("\n5. NESTED CONTEXT MANAGERS")
@contextmanager
def step(message):
"""Context manager for tracking steps"""
print(f" š¹ {message}")
try:
yield
finally:
print(f" ā
{message} done")
with step("Preparing data"):
print(" Inside step 1")
with step("Processing data"):
print(" Inside step 2")
time.sleep(0.1)
# ============================================================
# CONTEXT MANAGER THAT RETURNS A VALUE
# ============================================================
print("\n6. CONTEXT MANAGER THAT RETURNS A VALUE")
@contextmanager
def counter():
"""Context manager that returns a counter"""
count = 0
print(" Counter started")
try:
yield count
finally:
print(f" Counter finished: {count}")
with counter() as c:
c += 1
c += 1
print(f" Inside: count = {c}")
# ============================================================
# CLASS VS CONTEXTLIB
# ============================================================
print("\n" + "-" * 30)
print("CLASS VS CONTEXTLIB")
print("-" * 30)
print("""
āāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā FEATURE ā CLASS ā CONTEXTLIB ā
āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Code length ā More code ā Less code ā
ā ā ā ā
ā Readability ā Very clear ā Very clear ā
ā ā ā ā
ā Complexity ā Good for complex logic ā Great for simple logic ā
ā ā ā ā
ā Exception handling ā Manual (__exit__) ā Automatic (try/finally) ā
ā ā ā ā
ā Use case ā Complex setup/cleanup ā Simple setup/cleanup ā
āāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
š Use contextlib for simple context managers. Use classes for complex ones.
""")
contextlib key points:
- @contextmanager ā decorator that creates context managers
- yield ā the resource is yielded to the
withblock - try/finally ā ensures cleanup happens
- Less code ā simpler than writing classes
- Exception handling ā automatic with try/except
Quick Check: What decorator from contextlib creates a context manager? (Answer: @contextmanager)
Real-World Example
Building a Database Connection Manager
# Real-World Example: Database Connection Manager
import time
import random
from contextlib import contextmanager
from dataclasses import dataclass
from typing import Optional, List, Dict, Any
print("=" * 60)
print("DATABASE CONNECTION MANAGER")
print("=" * 60)
# ============================================================
# SIMULATED DATABASE
# ============================================================
class FakeDatabase:
"""Simulated database for demonstration"""
def __init__(self):
self.connected = False
self.transactions = []
self.data = {
"users": [
{"id": 1, "name": "Alice", "email": "alice@example.com"},
{"id": 2, "name": "Bob", "email": "bob@example.com"},
{"id": 3, "name": "Charlie", "email": "charlie@example.com"}
]
}
def connect(self):
"""Connect to the database"""
if self.connected:
raise RuntimeError("Already connected")
self.connected = True
print(" ā
Connected to database")
def disconnect(self):
"""Disconnect from the database"""
if not self.connected:
return
self.connected = False
print(" š Disconnected from database")
def query(self, sql: str) -> List[Dict[str, Any]]:
"""Execute a query"""
if not self.connected:
raise RuntimeError("Not connected to database")
print(f" š Query: {sql}")
time.sleep(0.5) # Simulate query time
if sql.lower() == "select * from users":
return self.data["users"]
elif sql.lower() == "select * from users where active = true":
return [u for u in self.data["users"] if u.get("active", True)]
else:
return []
# ============================================================
# CONTEXT MANAGER: CLASS APPROACH
# ============================================================
class DatabaseConnection:
"""Database connection context manager"""
def __init__(self, db: FakeDatabase):
self.db = db
def __enter__(self):
print(" š Acquiring database connection...")
self.db.connect()
return self.db
def __exit__(self, exc_type, exc_val, exc_tb):
print(" š Releasing database connection...")
if exc_type:
print(f" ā ļø Exception occurred: {exc_val}")
self.db.disconnect()
return False # Don't suppress exceptions
# ============================================================
# CONTEXT MANAGER: CONTEXTLIB APPROACH
# ============================================================
@contextmanager
def db_transaction(db: FakeDatabase):
"""Database transaction context manager"""
print(" š Starting transaction...")
db.connect()
try:
yield db
print(" ā
Transaction committed")
except Exception as e:
print(f" ā Transaction rolled back: {e}")
raise
finally:
db.disconnect()
# ============================================================
# CONTEXT MANAGER: RETRY ON ERROR
# ============================================================
@contextmanager
def retry_on_error(max_retries: int = 3, delay: float = 1.0):
"""Context manager that retries on errors"""
attempts = 0
while attempts < max_retries:
try:
yield
return # Success, exit the context
except Exception as e:
attempts += 1
print(f" ā ļø Attempt {attempts} failed: {e}")
if attempts < max_retries:
print(f" š Retrying in {delay}s...")
time.sleep(delay)
else:
print(f" ā All {max_retries} attempts failed")
raise
# ============================================================
# DEMONSTRATION
# ============================================================
print("\n1. BASIC DATABASE CONNECTION")
db = FakeDatabase()
with DatabaseConnection(db) as conn:
users = conn.query("SELECT * FROM users")
for user in users:
print(f" š¤ {user['name']} - {user['email']}")
print("\n2. DATABASE TRANSACTION")
try:
with db_transaction(db) as conn:
users = conn.query("SELECT * FROM users")
print(f" š Found {len(users)} users")
# Simulate an error
# raise ValueError("Something went wrong!")
except Exception as e:
print(f" ā Transaction failed: {e}")
print("\n3. RETRY ON ERROR")
error_count = 0
with retry_on_error(max_retries=3, delay=0.5):
error_count += 1
print(f" š Attempt {error_count}")
# Simulate random failure
if random.random() < 0.7: # 70% chance of failure
raise ConnectionError("Database connection lost!")
print(" ā
Success!")
print("\n4. COMBINED: RETRY + TRANSACTION")
db2 = FakeDatabase()
try:
with retry_on_error(max_retries=2, delay=0.5):
with db_transaction(db2) as conn:
users = conn.query("SELECT * FROM users")
print(f" š Successfully retrieved {len(users)} users")
except Exception as e:
print(f" ā Failed: {e}")
print("\n" + "=" * 60)
print("KEY TAKEAWAYS:")
print("=" * 60)
print("ā
Context managers make resource management automatic")
print("ā
Database connections are properly opened and closed")
print("ā
Transactions are committed or rolled back automatically")
print("ā
Retry logic can be wrapped in a context manager")
print("ā
Multiple context managers can be combined")
print("ā
Class and contextlib approaches both have their uses")
Real-world example key points:
- DatabaseConnection ā class-based context manager
- db_transaction ā contextlib-based transaction manager
- retry_on_error ā context manager that retries on failure
- Combined ā context managers can be nested
- Automatic cleanup ā connections are always closed
Quick Check: What happens to a database connection when an error occurs in a context manager? (Answer: It's still properly closed because __exit__ is called even with errors)
Best Practices
Using Context Managers Effectively
# Best Practices for Context Managers
print("=" * 60)
print("BEST PRACTICES FOR CONTEXT MANAGERS")
print("=" * 60)
from contextlib import contextmanager
# ============================================================
# 1. USE CONTEXT MANAGERS FOR RESOURCES
# ============================================================
print("\n1. USE CONTEXT MANAGERS FOR RESOURCES")
# ā
GOOD: Use context managers for files, connections, locks
with open("file.txt", "w") as f:
f.write("data")
# ā BAD: Manual management (error-prone)
# f = open("file.txt", "w")
# f.write("data")
# f.close() # Easy to forget!
print(" ā
Context managers handle resources automatically")
# ============================================================
# 2. USE CONTEXTLIB FOR SIMPLE CONTEXT MANAGERS
# ============================================================
print("\n2. USE CONTEXTLIB FOR SIMPLE CONTEXT MANAGERS")
# ā
GOOD: Use contextlib for simple logic
@contextmanager
def simple_timer():
import time
start = time.perf_counter()
try:
yield
finally:
print(f" Took {time.perf_counter() - start:.4f}s")
# ā BAD: Writing a class for simple logic (overkill)
# class Timer:
# def __enter__(self): ...
# def __exit__(self, *args): ...
print(" ā
contextlib is simpler for basic cases")
# ============================================================
# 3. USE CLASSES FOR COMPLEX CONTEXT MANAGERS
# ============================================================
print("\n3. USE CLASSES FOR COMPLEX CONTEXT MANAGERS")
# ā
GOOD: Class for complex setup/cleanup
class ComplexResource:
def __init__(self, config):
self.config = config
self.resource = None
def __enter__(self):
# Complex setup
self.resource = self._setup()
return self.resource
def __exit__(self, *args):
# Complex cleanup
self._cleanup()
def _setup(self): pass
def _cleanup(self): pass
print(" ā
Classes are better for complex logic")
# ============================================================
# 4. USE NESTED CONTEXT MANAGERS
# ============================================================
print("\n4. USE NESTED CONTEXT MANAGERS")
# ā
GOOD: Nested for multiple resources
with open("input.txt", "r") as source:
with open("output.txt", "w") as dest:
data = source.read()
dest.write(data)
# ā
GOOD: Multiple in one line (for simple cases)
with open("input.txt", "r") as source, open("output.txt", "w") as dest:
data = source.read()
dest.write(data)
print(" ā
Both ways work, choose what's readable")
# ============================================================
# 5. HANDLE EXCEPTIONS PROPERLY
# ============================================================
print("\n5. HANDLE EXCEPTIONS PROPERLY")
@contextmanager
def safe_resource():
print(" Acquiring resource")
try:
yield
except Exception as e:
print(f" Error during operation: {e}")
raise # Re-raise the exception
finally:
print(" Cleaning up resource")
print(" ā
Always clean up, even on errors")
# ============================================================
# 6. DON'T SUPPRESS EXCEPTIONS UNLESS INTENTIONAL
# ============================================================
print("\n6. DON'T SUPPRESS EXCEPTIONS WITHOUT REASON")
# ā BAD: Suppressing all exceptions (dangerous!)
class BadManager:
def __enter__(self): pass
def __exit__(self, *args):
return True # Suppresses ALL exceptions!
# ā
GOOD: Only suppress specific exceptions you want to ignore
from contextlib import suppress
with suppress(FileNotFoundError):
open("missing.txt", "r")
print(" ā
Only suppress exceptions you intend to ignore")
# ============================================================
# 7. SUMMARY
# ============================================================
print("\n" + "=" * 60)
print("BEST PRACTICES SUMMARY")
print("=" * 60)
print("""
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā PRACTICE ā WHY IT MATTERS ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Use context managers for ā Automatic resource management ā
ā resources ā ā
ā ā ā
ā Use contextlib for simple ā Less code, easier to read ā
ā cases ā ā
ā ā ā
ā Use classes for complex ā Better organization for complex logic ā
ā cases ā ā
ā ā ā
ā Nest context managers ā Handle multiple resources safely ā
ā ā ā
ā Handle exceptions properly ā Cleanup happens even with errors ā
ā ā ā
ā Don't suppress exceptions ā Only ignore errors you mean to ignore ā
ā without reason ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
š Context managers make your code safer, cleaner, and more reliable.
""")
Best practices summary:
- Use for resources ā files, connections, locks
- Use contextlib for simple ā less code
- Use classes for complex ā better organization
- Nest context managers ā handle multiple resources
- Handle exceptions ā cleanup always happens
- Don't suppress blindly ā only ignore intentional errors
Quick Check: What's the best way to handle multiple resources? (Answer: Nest context managers or use multiple in one with statement)
Try It Yourself
Experiment with context managers in the editor below.
CONTEXT MANAGERS - PRACTICE
==================================================
1. BUILT-IN CONTEXT MANAGERS
File content: Hello, Context Managers!
2. CLASS-BASED CONTEXT MANAGER
šØļø Printer ready
Printing: Hello, World!
Printing: This is a test
šØļø Printer done
3. CONTEXTLIB CONTEXT MANAGER
š Loading data
Data loaded
ā Loading data complete
š Processing data
Data processed
ā Processing data complete
4. CONTEXT MANAGER THAT RETURNS A VALUE
Inside: 3
Final count: 3
5. NESTED CONTEXT MANAGERS
Two files created
Two files created (one line)
You've Got It!
You now understand context managers in Python. You know how to use the with statement, use built-in context managers, and create your own using both class and contextlib approaches.
Quick Quiz
Test what you've learned:
Frequently Asked Questions
What is a context manager in Python?
with statement and ensures resources are properly released even if errors occur.
What's the difference between class and contextlib context managers?
__enter__ and __exit__ methods. contextlib uses the @contextmanager decorator with yield. Classes are better for complex logic, while contextlib is simpler for basic cases.
Can I use multiple context managers at once?
with statement. For example: with open('a') as f1, open('b') as f2: or nested with statements.
Does cleanup happen if an error occurs?
__exit__ method (or the finally block in contextlib) is always called, even if an exception occurs in the with block.
Can I suppress exceptions in a context manager?
True from __exit__. In contextlib, use a try/except around the yield and don't re-raise the exception. However, only suppress exceptions you intentionally want to ignore.
What are some common built-in context managers?
open() for files, threading.Lock, tempfile.NamedTemporaryFile, contextlib.suppress, contextlib.chdir, and contextlib.redirect_stdout.
Where to Go From Here
Now that you understand context managers in Python, check out these related topics:
Decorators
Learn about decorators ā another way to wrap functions.
Learn More āGenerators
Learn about generators ā the foundation of contextlib.
Learn More āAsync/Await
Learn about async context managers with async with.
Learn More ā