__enter__ and __exit__
Overview
The `__enter__` and `__exit__` dunder methods define the context manager protocol. This is the set of methods an object must implement to support the `with` statement. Think of them as automatic setup and teardown hooks for an object's lifecycle. - `__enter__(self)`: Executes setup logic before the block starts. It RETURNS the value assigned to the `as` variable (often `self` or `None`). If it fails, the exception propagates and `__exit__` is skipped. - `__exit__(self, exc_type, exc_val, traceback)`: Executes teardown logic when the block ends. It RETURNS a boolean indicating if an exception should be suppressed. If it fails, the new exception replaces the original one. When `with Obj() as x:` runs, Python calls `Obj.__enter__()` and assigns the result to `x`. When the block ends, normally or via an exception, Python calls `Obj.__exit__()`. class SilentFail: def __enter__(self): return self def __exit__(self, exc_type, exc_val, traceback): # Returning True swallows the exception! return True with SilentFail(): raise ValueError("This will be hidden") A common trap is accidentally swallowing exceptions by returning `True` (or a truthy value) from `__exit__`. This completely suppresses the error, making debugging difficult. Simpler ways to write context managers are covered fully in `contextmanager-decorator`.
They guarantee cleanup (like closing files or releasing locks) happens reliably, even if the code inside the block crashes. This replaces repetitive `try`/`finally` blocks.
Where used: Custom database sessions, Temporary file management, Resource locks
Why learn this
- Write custom resources that cleanly manage their own state.
- Understand how built-in context managers (like `open()`) work under the hood.
Code walkthrough
class Timer:
def __enter__(self):
print('Start')
return self
def __exit__(self, t, v, tb):
print('End')
with Timer():
print('Working')
Focus: with Timer():
Aha moment
class FakeFile:
def __enter__(self):
return 'data'
def __exit__(self, *args):
pass
with FakeFile() as f:
print(type(f).__name__)
Prediction: What type is printed?
Common guess: FakeFile
The variable `f` gets whatever `__enter__` returns, not necessarily the context manager instance itself. Since it returns the string `'data'`, `f` is a string.
Common mistakes
- Forgetting to return self in `__enter__`: The value returned by `__enter__` is assigned to the variable after `as`. If you forget to return `self`, that variable becomes `None`.
- Forgetting the exit signature: The `__exit__` method requires three arguments (`exc_type`, `exc_val`, `traceback`) after `self`. Omitting them causes a `TypeError`.
- Accidentally returning True from `__exit__`: If `__exit__` returns `True`, any exception raised in the `with` block is swallowed. Only return `True` if you explicitly intend to suppress the error.
Glossary
- suppressed
- Preventing an error or exception from stopping the program, effectively hiding or neutralizing it. Example: `return True`
- hooks
- Special functions or methods that let you insert your own code into the normal sequence of a program's execution. Example: `def __enter__(self):`
Recall questions
- What triggers the `__enter__` and `__exit__` methods of an object?
- How does `__exit__` signal that an exception should be suppressed?
- What values are bound to the `as` variable in a `with` statement?
Understanding checks
What is printed to the console?
Enter Value Exit
`__enter__` is called first, printing `'Enter'`. Its return value `'Value'` is bound to `d`. The block prints `d` (`'Value'`), and finally `__exit__` runs, printing `'Exit'`.
What is printed to the console?
Caught: ValueError Done
The `ValueError` is caught by `__exit__`, which prints it. Because `__exit__` returns `True`, the exception is suppressed and execution continues to print `'Done'`.
Practice tasks
Modify Exception Handling
Given this context manager, change it so that it suppresses ONLY `KeyError` exceptions, and lets everything else propagate.
Challenge
A Simple Transaction Context
Write a `Transaction` context manager that takes a dictionary. In `__enter__`, it should return a shallow copy of the dictionary. In `__exit__`, if no exception occurred, it should update the original dictionary with the copy's contents.
Continue learning
Previous: Class Creation and Instantiation
Previous: try/except with specific exception types
Next: contextlib.contextmanager decorator
Next: Async context managers