Context Manager Protocol
Learning Objectives
- By the end of this lesson, you will be able to:
- - Understand the context manager protocol
- - Implement `__enter__` and `__exit__` methods
- - Handle exceptions in context managers
- - Create custom context managers
- - Understand resource management
- - Use context managers effectively
- - Handle cleanup operations
- - Understand the `with` statement
- - Create reusable context managers
- - Debug context manager issues
Lesson 14.1: Context Manager Protocol
Learning Objectives
By the end of this lesson, you will be able to:
- Understand the context manager protocol
- Implement
__enter__and__exit__methods - Handle exceptions in context managers
- Create custom context managers
- Understand resource management
- Use context managers effectively
- Handle cleanup operations
- Understand the
withstatement - Create reusable context managers
- Debug context manager issues
Introduction to Context Managers
Context managers are objects that manage resources and ensure proper setup and cleanup. They are used with the with statement to guarantee that resources are properly acquired and released.
Why Context Managers?
- Automatic cleanup: Resources are always released
- Exception safety: Cleanup happens even if errors occur
- Clean code: Reduces boilerplate code
- Resource management: Ensures proper resource handling
- Pythonic: Idiomatic Python pattern
The with Statement
The with statement is used to enter and exit a context manager:
with open('file.txt', 'r') as f:
content = f.read()
# File is automatically closed here
Context Manager Protocol
A context manager must implement two methods:
__enter__(): Called when entering thewithblock__exit__(): Called when exiting thewithblock
Basic Context Manager
class MyContextManager:
def __enter__(self):
print("Entering context")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("Exiting context")
return False # Don't suppress exceptions
# Use context manager
with MyContextManager() as cm:
print("Inside context")
# Output:
# Entering context
# Inside context
# Exiting context
Understanding __enter__
The __enter__ method:
- Is called when entering the
withblock - Can return a value (assigned to the variable after
as) - Typically returns
selfor a resource
class FileManager:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
print(f"Opening {self.filename}")
self.file = open(self.filename, self.mode)
return self.file # Return the file object
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"Closing {self.filename}")
if self.file:
self.file.close()
return False
# Use context manager
with FileManager('test.txt', 'w') as f:
f.write("Hello, World!")
# File is automatically closed
Understanding __exit__
The __exit__ method receives three arguments:
exc_type: Exception type (None if no exception)exc_val: Exception value (None if no exception)exc_tb: Exception traceback (None if no exception)
class MyContextManager:
def __enter__(self):
print("Entering")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"Exiting - Exception: {exc_type}")
if exc_type is not None:
print(f"Exception occurred: {exc_val}")
return False # Don't suppress exception
with MyContextManager():
print("Inside")
# raise ValueError("Error!") # Uncomment to see exception handling
Return Value of __exit__
The __exit__ method can return:
FalseorNone: Exception is propagated (normal behavior)True: Exception is suppressed
class SuppressException:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is ValueError:
print(f"Suppressing ValueError: {exc_val}")
return True # Suppress the exception
return False # Don't suppress other exceptions
with SuppressException():
raise ValueError("This will be suppressed")
print("Continuing after suppressed exception")
Practical Context Manager Examples
Example 1: File Manager
class FileManager:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
print(f"Opening {self.filename}")
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
print(f"Closing {self.filename}")
self.file.close()
return False
# Use context manager
with FileManager('data.txt', 'w') as f:
f.write("Hello, World!")
# File is automatically closed
Example 2: Timer Context Manager
import time
class Timer:
def __init__(self, name="Operation"):
self.name = name
self.start_time = None
def __enter__(self):
print(f"Starting {self.name}...")
self.start_time = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
elapsed = time.time() - self.start_time
print(f"{self.name} took {elapsed:.4f} seconds")
return False
# Use context manager
with Timer("Data processing"):
time.sleep(1)
# Simulate work
# Output: Data processing took 1.0000 seconds
Example 3: Database Connection Manager
class DatabaseConnection:
def __init__(self, connection_string):
self.connection_string = connection_string
self.connection = None
def __enter__(self):
print(f"Connecting to {self.connection_string}")
# Simulate connection
self.connection = {"connected": True, "string": self.connection_string}
return self.connection
def __exit__(self, exc_type, exc_val, exc_tb):
if self.connection:
print(f"Closing connection to {self.connection_string}")
self.connection["connected"] = False
return False
# Use context manager
with DatabaseConnection("postgresql://localhost/db") as conn:
print(f"Using connection: {conn}")
# Use connection
# Connection is automatically closed
Example 4: Lock Manager
import threading
class LockManager:
def __init__(self, lock):
self.lock = lock
def __enter__(self):
print("Acquiring lock")
self.lock.acquire()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("Releasing lock")
self.lock.release()
return False
# Use context manager
lock = threading.Lock()
with LockManager(lock):
print("Critical section")
# Lock is automatically released
Example 5: Change Directory Context Manager
import os
class ChangeDirectory:
def __init__(self, path):
self.path = path
self.original_path = None
def __enter__(self):
self.original_path = os.getcwd()
print(f"Changing directory to {self.path}")
os.chdir(self.path)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"Restoring directory to {self.original_path}")
os.chdir(self.original_path)
return False
# Use context manager
with ChangeDirectory("/tmp"):
print(f"Current directory: {os.getcwd()}")
# Work in /tmp
# Directory is automatically restored
Exception Handling in Context Managers
Handling Exceptions
Context managers can handle exceptions in the __exit__ method:
class ExceptionHandler:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None:
print(f"Handling exception: {exc_type.__name__}: {exc_val}")
# Log exception, clean up, etc.
return False # Don't suppress exception
with ExceptionHandler():
raise ValueError("Test exception")
# Exception is handled but still propagated
Suppressing Exceptions
You can suppress exceptions by returning True from __exit__:
class SuppressErrors:
def __init__(self, *exception_types):
self.exception_types = exception_types
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type in self.exception_types:
print(f"Suppressing {exc_type.__name__}: {exc_val}")
return True # Suppress exception
return False # Don't suppress other exceptions
with SuppressErrors(ValueError, TypeError):
raise ValueError("This will be suppressed")
print("Continuing after suppressed exception")
Cleanup on Exception
Context managers ensure cleanup happens even when exceptions occur:
class ResourceManager:
def __init__(self, resource_name):
self.resource_name = resource_name
self.resource = None
def __enter__(self):
print(f"Acquiring {self.resource_name}")
self.resource = {"name": self.resource_name, "acquired": True}
return self.resource
def __exit__(self, exc_type, exc_val, exc_tb):
if self.resource:
print(f"Releasing {self.resource_name}")
self.resource["acquired"] = False
if exc_type is not None:
print(f"Exception occurred: {exc_val}")
return False
# Exception occurs, but resource is still released
with ResourceManager("Database"):
raise ValueError("Error!")
# Resource is still released
Exception Chaining
You can re-raise exceptions or raise new ones:
class ErrorLogger:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None:
print(f"Logging exception: {exc_type.__name__}: {exc_val}")
# Log to file, database, etc.
# You can raise a new exception here if needed
return False # Propagate original exception
with ErrorLogger():
raise ValueError("Original error")
Advanced Context Manager Patterns
Pattern 1: Context Manager with State
class StatefulContext:
def __init__(self, initial_state):
self.initial_state = initial_state
self.current_state = None
def __enter__(self):
self.current_state = self.initial_state.copy() if hasattr(self.initial_state, 'copy') else self.initial_state
print(f"Entering with state: {self.current_state}")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"Exiting with state: {self.current_state}")
return False
def update_state(self, new_state):
self.current_state = new_state
with StatefulContext({"count": 0}) as ctx:
ctx.update_state({"count": 5})
print(f"State: {ctx.current_state}")
Pattern 2: Nested Context Managers
class Context1:
def __enter__(self):
print("Entering Context1")
return self
def __exit__(self, *args):
print("Exiting Context1")
return False
class Context2:
def __enter__(self):
print("Entering Context2")
return self
def __exit__(self, *args):
print("Exiting Context2")
return False
# Nested context managers
with Context1():
with Context2():
print("Inside both contexts")
# Output:
# Entering Context1
# Entering Context2
# Inside both contexts
# Exiting Context2
# Exiting Context1
Pattern 3: Multiple Context Managers
# Multiple context managers in one with statement
with Context1(), Context2():
print("Inside both contexts")
# Output:
# Entering Context1
# Entering Context2
# Inside both contexts
# Exiting Context2
# Exiting Context1
Pattern 4: Context Manager Factory
def create_timer(name):
"""Factory function for Timer context manager."""
class Timer:
def __init__(self, name):
self.name = name
self.start_time = None
def __enter__(self):
import time
print(f"Starting {self.name}...")
self.start_time = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
import time
elapsed = time.time() - self.start_time
print(f"{self.name} took {elapsed:.4f} seconds")
return False
return Timer(name)
# Use factory
with create_timer("Operation"):
import time
time.sleep(1)
Pattern 5: Reusable Context Manager
class ReusableContext:
def __init__(self):
self.count = 0
def __enter__(self):
self.count += 1
print(f"Entering (use #{self.count})")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"Exiting (use #{self.count})")
return False
# Reuse the same context manager
ctx = ReusableContext()
with ctx:
print("First use")
with ctx:
print("Second use")
Common Mistakes and Pitfalls
1. Not Returning from __exit__
# WRONG: Not returning False
class BadContext:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
pass # Implicitly returns None (OK, but explicit is better)
# CORRECT: Explicitly return False
class GoodContext:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
return False # Explicit
2. Not Handling Exceptions Properly
# WRONG: Suppressing all exceptions
class BadContext:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
return True # Suppresses ALL exceptions (dangerous!)
# CORRECT: Only suppress specific exceptions
class GoodContext:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is ValueError:
return True # Only suppress ValueError
return False # Don't suppress others
3. Not Cleaning Up on Exception
# WRONG: Cleanup might not happen
class BadContext:
def __init__(self):
self.resource = acquire_resource()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None: # Only cleanup if no exception
release_resource(self.resource)
# CORRECT: Always cleanup
class GoodContext:
def __init__(self):
self.resource = None
def __enter__(self):
self.resource = acquire_resource()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self.resource:
release_resource(self.resource) # Always cleanup
return False
4. Forgetting to Return Resource
# WRONG: Not returning the resource
class BadContext:
def __enter__(self):
self.file = open('file.txt', 'r')
# Forgot to return!
def __exit__(self, *args):
self.file.close()
# CORRECT: Return the resource
class GoodContext:
def __enter__(self):
self.file = open('file.txt', 'r')
return self.file # Return the resource
def __exit__(self, *args):
self.file.close()
Best Practices
1. Always Clean Up Resources
class ResourceManager:
def __enter__(self):
self.resource = acquire_resource()
return self.resource
def __exit__(self, exc_type, exc_val, exc_tb):
if self.resource:
release_resource(self.resource) # Always cleanup
return False
2. Handle Exceptions Appropriately
class ExceptionHandler:
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None:
# Handle exception
log_exception(exc_type, exc_val, exc_tb)
return False # Don't suppress unless necessary
3. Return Appropriate Values
class FileManager:
def __enter__(self):
self.file = open('file.txt', 'r')
return self.file # Return the resource, not self
def __exit__(self, *args):
self.file.close()
return False
4. Document Your Context Managers
class MyContextManager:
"""Context manager that does something.
Usage:
with MyContextManager() as cm:
# Use cm
"""
def __enter__(self):
"""Enter the context."""
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Exit the context."""
return False
5. Test Exception Handling
# Test that exceptions are handled correctly
ctx = MyContextManager()
try:
with ctx:
raise ValueError("Test")
except ValueError:
print("Exception was propagated correctly")
Practice Exercise
Exercise: Custom Context Managers
Objective: Create a Python program that demonstrates custom context managers.
Instructions:
-
Create a file called
context_managers_practice.py -
Write a program that:
- Creates custom context managers
- Implements
__enter__and__exit__ - Handles exceptions in context managers
- Demonstrates practical applications
- Shows advanced patterns
-
Your program should include:
- Basic context manager
- File manager context manager
- Timer context manager
- Exception handling
- Resource cleanup
- Nested context managers
- Real-world examples
Example Solution:
"""
Context Manager Protocol Practice
This program demonstrates custom context managers.
"""
import time
import os
print("=" * 60)
print("CONTEXT MANAGER PROTOCOL PRACTICE")
print("=" * 60)
print()
# 1. Basic context manager
print("1. BASIC CONTEXT MANAGER")
print("-" * 60)
class MyContextManager:
def __enter__(self):
print("Entering context")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("Exiting context")
return False
with MyContextManager() as cm:
print("Inside context")
print()
# 2. File manager context manager
print("2. FILE MANAGER CONTEXT MANAGER")
print("-" * 60)
class FileManager:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
print(f"Opening {self.filename}")
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
print(f"Closing {self.filename}")
self.file.close()
return False
# Create a test file
with FileManager('test.txt', 'w') as f:
f.write("Hello, World!")
with FileManager('test.txt', 'r') as f:
content = f.read()
print(f"Content: {content}")
print()
# 3. Timer context manager
print("3. TIMER CONTEXT MANAGER")
print("-" * 60)
class Timer:
def __init__(self, name="Operation"):
self.name = name
self.start_time = None
def __enter__(self):
print(f"Starting {self.name}...")
self.start_time = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
elapsed = time.time() - self.start_time
print(f"{self.name} took {elapsed:.4f} seconds")
return False
with Timer("Data processing"):
time.sleep(0.1)
# Simulate work
print()
# 4. Exception handling in context manager
print("4. EXCEPTION HANDLING")
print("-" * 60)
class ExceptionHandler:
def __enter__(self):
print("Entering context")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None:
print(f"Handling exception: {exc_type.__name__}: {exc_val}")
print("Exiting context")
return False # Don't suppress exception
try:
with ExceptionHandler():
raise ValueError("Test exception")
except ValueError as e:
print(f"Caught exception: {e}")
print()
# 5. Suppressing exceptions
print("5. SUPPRESSING EXCEPTIONS")
print("-" * 60)
class SuppressErrors:
def __init__(self, *exception_types):
self.exception_types = exception_types
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type in self.exception_types:
print(f"Suppressing {exc_type.__name__}: {exc_val}")
return True # Suppress exception
return False
with SuppressErrors(ValueError, TypeError):
raise ValueError("This will be suppressed")
print("Continuing after suppressed exception")
print()
# 6. Resource manager
print("6. RESOURCE MANAGER")
print("-" * 60)
class ResourceManager:
def __init__(self, resource_name):
self.resource_name = resource_name
self.resource = None
def __enter__(self):
print(f"Acquiring {self.resource_name}")
self.resource = {"name": self.resource_name, "acquired": True}
return self.resource
def __exit__(self, exc_type, exc_val, exc_tb):
if self.resource:
print(f"Releasing {self.resource_name}")
self.resource["acquired"] = False
if exc_type is not None:
print(f"Exception occurred: {exc_val}")
return False
# Normal usage
with ResourceManager("Database"):
print("Using resource")
# Exception occurs, but resource is still released
try:
with ResourceManager("Database"):
raise ValueError("Error!")
except ValueError:
print("Exception caught, resource was released")
print()
# 7. Change directory context manager
print("7. CHANGE DIRECTORY CONTEXT MANAGER")
print("-" * 60)
class ChangeDirectory:
def __init__(self, path):
self.path = path
self.original_path = None
def __enter__(self):
self.original_path = os.getcwd()
print(f"Changing directory to {self.path}")
os.chdir(self.path)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"Restoring directory to {self.original_path}")
os.chdir(self.original_path)
return False
original = os.getcwd()
with ChangeDirectory("/tmp"):
print(f"Current directory: {os.getcwd()}")
print(f"Restored directory: {os.getcwd()}")
print()
# 8. Stateful context manager
print("8. STATEFUL CONTEXT MANAGER")
print("-" * 60)
class StatefulContext:
def __init__(self, initial_state):
self.initial_state = initial_state
self.current_state = None
def __enter__(self):
self.current_state = self.initial_state.copy() if isinstance(self.initial_state, dict) else self.initial_state
print(f"Entering with state: {self.current_state}")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"Exiting with state: {self.current_state}")
return False
def update_state(self, new_state):
self.current_state = new_state
with StatefulContext({"count": 0}) as ctx:
ctx.update_state({"count": 5})
print(f"State: {ctx.current_state}")
print()
# 9. Nested context managers
print("9. NESTED CONTEXT MANAGERS")
print("-" * 60)
class Context1:
def __enter__(self):
print("Entering Context1")
return self
def __exit__(self, *args):
print("Exiting Context1")
return False
class Context2:
def __enter__(self):
print("Entering Context2")
return self
def __exit__(self, *args):
print("Exiting Context2")
return False
with Context1():
with Context2():
print("Inside both contexts")
print()
# 10. Multiple context managers
print("10. MULTIPLE CONTEXT MANAGERS")
print("-" * 60)
with Context1(), Context2():
print("Inside both contexts")
print()
# 11. Reusable context manager
print("11. REUSABLE CONTEXT MANAGER")
print("-" * 60)
class ReusableContext:
def __init__(self):
self.count = 0
def __enter__(self):
self.count += 1
print(f"Entering (use #{self.count})")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"Exiting (use #{self.count})")
return False
ctx = ReusableContext()
with ctx:
print("First use")
with ctx:
print("Second use")
print()
# 12. Context manager with return value
print("12. CONTEXT MANAGER WITH RETURN VALUE")
print("-" * 60)
class ValueContext:
def __init__(self, value):
self.value = value
def __enter__(self):
return self.value * 2 # Return modified value
def __exit__(self, *args):
return False
with ValueContext(5) as doubled:
print(f"Doubled value: {doubled}")
print()
# 13. Error logger context manager
print("13. ERROR LOGGER CONTEXT MANAGER")
print("-" * 60)
class ErrorLogger:
def __enter__(self):
self.errors = []
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None:
self.errors.append((exc_type.__name__, str(exc_val)))
print(f"Logged error: {exc_type.__name__}: {exc_val}")
return False
logger = ErrorLogger()
try:
with logger:
raise ValueError("Test error")
except ValueError:
print(f"Errors logged: {logger.errors}")
print()
# 14. Lock manager context manager
print("14. LOCK MANAGER CONTEXT MANAGER")
print("-" * 60)
import threading
class LockManager:
def __init__(self, lock):
self.lock = lock
def __enter__(self):
print("Acquiring lock")
self.lock.acquire()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("Releasing lock")
self.lock.release()
return False
lock = threading.Lock()
with LockManager(lock):
print("Critical section")
print()
# 15. Real-world: Database connection
print("15. REAL-WORLD: DATABASE CONNECTION")
print("-" * 60)
class DatabaseConnection:
def __init__(self, connection_string):
self.connection_string = connection_string
self.connection = None
def __enter__(self):
print(f"Connecting to {self.connection_string}")
# Simulate connection
self.connection = {"connected": True, "string": self.connection_string}
return self.connection
def __exit__(self, exc_type, exc_val, exc_tb):
if self.connection:
print(f"Closing connection to {self.connection_string}")
self.connection["connected"] = False
return False
with DatabaseConnection("postgresql://localhost/db") as conn:
print(f"Using connection: {conn}")
# Use connection
print()
# Cleanup
if os.path.exists('test.txt'):
os.remove('test.txt')
print("=" * 60)
print("PRACTICE COMPLETE!")
print("=" * 60)
Expected Output (truncated):
============================================================
CONTEXT MANAGER PROTOCOL PRACTICE
============================================================
1. BASIC CONTEXT MANAGER
------------------------------------------------------------
Entering context
Inside context
Exiting context
[... rest of output ...]
Challenge (Optional):
- Create a context manager that temporarily modifies environment variables
- Build a context manager that redirects stdout/stderr
- Implement a context manager that manages temporary files
- Create a context manager that handles database transactions
Key Takeaways
- Context managers - manage resources with
withstatement __enter__method - called when entering context__exit__method - called when exiting context- Exception handling -
__exit__receives exception info - Return value -
__exit__can suppress exceptions by returning True - Resource cleanup - always happens, even on exceptions
withstatement - ensures proper resource management- Nested contexts - can nest multiple context managers
- Multiple contexts - can use multiple in one
withstatement - Best practices - always cleanup, handle exceptions, return appropriate values
- Automatic cleanup - resources are always released
- Exception safety - cleanup happens even if errors occur
- Clean code - reduces boilerplate
- Pythonic - idiomatic Python pattern
- Reusable - can reuse context manager instances
Quiz: Context Protocol
Test your understanding with these questions:
-
What methods must a context manager implement?
- A)
__init__and__del__ - B)
__enter__and__exit__ - C)
startandstop - D)
openandclose
- A)
-
What does
__enter__return?- A) Nothing
- B) The context manager instance
- C) A value assigned to the variable after
as - D) Always None
-
What arguments does
__exit__receive?- A) None
- B) Exception type, value, traceback
- C) Self only
- D) Exception only
-
What happens if
__exit__returns True?- A) Exception is propagated
- B) Exception is suppressed
- C) Context manager is reused
- D) Nothing
-
When is
__exit__called?- A) Only on normal exit
- B) Only on exceptions
- C) Always, even on exceptions
- D) Never
-
What is the purpose of context managers?
- A) To manage resources
- B) To ensure cleanup
- C) To handle exceptions
- D) All of the above
-
Can you nest context managers?
- A) No
- B) Yes, but only two
- C) Yes, any number
- D) Only with special syntax
-
What does the
withstatement do?- A) Opens a file
- B) Enters and exits a context manager
- C) Handles exceptions
- D) All of the above
-
What should
__exit__return to suppress an exception?- A) False
- B) True
- C) None
- D) The exception
-
When should you use context managers?
- A) Always
- B) For resource management
- C) Never
- D) Only for files
Answers:
- B)
__enter__and__exit__(context manager protocol) - C) A value assigned to the variable after
as(__enter__return value) - B) Exception type, value, traceback (
__exit__arguments) - B) Exception is suppressed (returning True from
__exit__) - C) Always, even on exceptions (
__exit__is always called) - D) All of the above (context manager purposes)
- C) Yes, any number (nesting context managers)
- B) Enters and exits a context manager (
withstatement purpose) - B) True (suppress exception by returning True)
- B) For resource management (context manager use case)
Next Steps
Excellent work! You've mastered the context manager protocol. You now understand:
- How to implement
__enter__and__exit__ - How to handle exceptions in context managers
- How to create custom context managers
What's Next?
- Lesson 14.2: contextlib Module
- Learn the
@contextmanagerdecorator - Understand
closing()and other utilities - Explore contextlib helpers
Additional Resources
- Context Managers: docs.python.org/3/reference/datamodel.html#context-managers
- with Statement: docs.python.org/3/reference/compound_stmts.html#with
- PEP 343: peps.python.org/pep-0343/ (The "with" Statement)
Lesson completed! You're ready to move on to the next lesson.
Course Navigation
- Context Manager Protocol
- contextlib Module