Python Custom Context Managers
A custom context manager is a class that defines __enter__ and __exit__.
The two protocol methods
A context manager is any object with these two methods. Together they form the protocol.
class Hello:
def __enter__(self):
print("Entering")
return self
def __exit__(self, exc_type, exc_value, traceback):
print("Leaving")
with Hello():
print("Inside")What __enter__ returns
The return value of __enter__ becomes the as target. Here the block receives a plain string.
class Gift:
def __enter__(self):
return "a book"
def __exit__(self, exc_type, exc_value, traceback):
print("Gift closed")
with Gift() as present:
print(present)What __exit__ receives on an error
When the block raises, the three arguments describe that error. Returning False lets it spread.
class Watcher:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
print("Type:", exc_type.__name__)
print("Value:", exc_value)
return False
try:
with Watcher():
raise ValueError("bad number")
except ValueError:
print("It still escaped.")Returning True swallows the error
To swallow an error means to stop it and carry on. Only __exit__ can make that choice.
class Calm:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
print("Swallowed", exc_type.__name__)
return True
with Calm():
raise ValueError("bad number")
print("The program carries on.")A timing manager shape
A timer records a start moment on entry. On exit it stores the elapsed time and prints only a label.
import time
class Timer:
def __enter__(self):
self.start = time.perf_counter()
return self
def __exit__(self, exc_type, exc_value, traceback):
self.elapsed = time.perf_counter() - self.start
print("Timer stopped.")
with Timer() as timer:
total = sum(range(100))
print(total)Example
class Tag:
def __init__(self, name):
self.name = name
def __enter__(self):
print(f"<{self.name}>")
return self
def __exit__(self, exc_type, exc_value, traceback):
print(f"</{self.name}>")
return False
with Tag("section"):
print("Hello inside")
try:
with Tag("warning"):
raise ValueError("oops")
except ValueError:
print("Caught outside")Key points
- Protocol: a set of method names an object must provide to work a certain way
- __enter__: the method that runs when the block starts
- __exit__: the method that runs when the block ends, for any reason
- Traceback: the record Python keeps of where an error happened
- On an error, __exit__ receives the error type, the error value, and a traceback
- Use return True in __exit__ to stop an error from spreading
- A with block can then use objects from the class.
In this example, Tag stores the label name at creation. The __enter__ method prints the opening label. The first block prints its line inside the labels. The __exit__ method prints the closing label. The second block raises a ValueError. The closing label still prints during the exit. The method returns False, so the error spreads. The except block then catches it outside.
Note
Most __exit__ methods return None or False.
That lets errors spread as usual after cleanup runs.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Python Playground
Exercise
A block raises ValueError inside your custom manager. What does __exit__ receive?