__enter__ / __exit__
Introduction
Python’s __enter__ and __exit__ methods form the context manager protocol, which powers the with statement. This protocol provides a clean way to manage resource setup and teardown, ensuring that cleanup code runs reliably regardless of whether the block executes successfully or raises an exception.
The Protocol
__enter__(self)
This method is called when entering the with block. It can return any object, which becomes bound to the variable after the as keyword:
def __enter__(self):
# Setup logic here
return self # The object bound to 'as' in 'with ... as x:'
__exit__(self, exc_type, exc_value, exc_tb)
This method is called when exiting the with block, regardless of how the block ends. It receives information about any exception that occurred:
def __exit__(self, exc_type, exc_value, exc_tb):
# Cleanup logic here
# Return True to suppress the exception
return False
The parameters are:
exc_type— Exception class (e.g.,ValueError) orNoneif no exception occurredexc_value— Exception instance orNoneexc_tb— Traceback object orNone
Practical Examples
Basic Resource Management
class DatabaseConnection:
def __enter__(self):
print("Connecting to database...")
self.connection = "db_connection_object"
return self
def __exit__(self, exc_type, exc_value, exc_tb):
print("Closing database connection...")
self.connection = None
return False
with DatabaseConnection() as db:
print(f"Using: {db.connection}")
Output:
Connecting to database...
Using: db_connection_object
Closing database connection...
Suppressing Specific Exceptions
You can selectively suppress certain exceptions while allowing others to propagate:
class IgnoreValueError:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, exc_tb):
if exc_type is ValueError:
print(f"Suppressing ValueError: {exc_value}")
return True # Suppress this exception
return False # Let other exceptions propagate
# This exception is suppressed
with IgnoreValueError():
raise ValueError("This will be suppressed")
# This exception propagates
try:
with IgnoreValueError():
raise TypeError("This will NOT be suppressed")
except TypeError:
print("TypeError was propagated correctly")
Logging Exceptions
The __exit__ method can access exception details for logging or custom handling:
class ExceptionLogger:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, exc_tb):
if exc_type is not None:
print(f"Exception: {exc_type.__name__}: {exc_value}")
else:
print("No exception occurred")
return False
with ExceptionLogger():
x = 1 / 0
Output:
Exception: ZeroDivisionError: division by zero
Common Patterns
Returning a Different Object
The object returned by __enter__ (not the context manager itself) is bound to the as variable:
class Outer:
def __enter__(self):
return "returned_value"
with Outer() as x:
print(x) # Prints: returned_value
Conditional Suppression
Handle different exception types differently:
def __exit__(self, exc_type, exc_value, exc_tb):
if exc_type is ValueError:
print("Handling ValueError")
return True # Suppress
elif exc_type is TypeError:
print("Handling TypeError, will re-raise")
return False # Re-raise
return False
See Also
__init__— Initialize objects when created__del__— Cleanup when objects are garbage collected__call__— Make instances callable like functions
Summary
| Method | Purpose | Return Value |
|---|---|---|
__enter__ | Setup resources | Object bound to as clause |
__exit__ | Cleanup and exception handling | True to suppress, False/None to propagate |
The context manager protocol is ideal for managing resources that require explicit cleanup, such as files, database connections, locks, and temporary files. It guarantees that cleanup code runs even when exceptions occur, making your code more reliable and readable.