A context manager is an object that works with the with statement. Python calls its __enter__ method at the start of the block and its __exit__ method at the end, even when the block raises an error. open() is the best-known example: its __exit__ closes the file.
The program
import time class Timer: def __init__(self, label): self.label = label def __enter__(self): self.start = time.perf_counter() return self # this is the value after "as" def __exit__(self, exc_type, exc_value, traceback): self.elapsed = time.perf_counter() - self.start print(f"{self.label}: {self.elapsed:.1f} s") return False # do not hide errors with Timer("Short pause") as t: time.sleep(0.1) print("Stored on the object:", round(t.elapsed, 1))
Short pause: 0.1 s Stored on the object: 0.1
How it works
with Timer(...) as tcreates the object, calls__enter__, and stores what__enter__returns int.- When the block ends, Python calls
__exit__. If the block raised an error, the three arguments describe it. If not, all three areNone. - If
__exit__returnsTrue, the error is hidden and the program continues. ReturningFalse(or nothing) lets the error continue as normal. Hiding errors is rarely what you want. time.perf_counter()is the clock to use for measuring short durations. The times are rounded to one decimal place. The real time is a little more than 0.1 seconds, so on a slow or busy computer the output can be slightly different.
The shorter way: contextlib
For simple cases, @contextmanager turns a generator function into a context manager. The code before yield is the "enter" part, and the code in finally is the "exit" part:
from contextlib import contextmanager @contextmanager def section(title): print(f"--- {title} ---") try: yield finally: print(f"--- end of {title} ---") with section("Report"): print("Sales were up.") try: with section("Broken"): raise ValueError("bad data") except ValueError as e: print("Caught:", e)
--- Report --- Sales were up. --- end of Report --- --- Broken --- --- end of Broken --- Caught: bad data
The second block raised an error, but the closing line was still printed. The finally makes sure of that, in the same way that __exit__ always runs.