
You already use them all the time: Python Context Managers.
with open("data.txt", "r") as f:
data = f.read()This with block is a Context Manager. It guarantees that f.close() is called when the block ends, even if an error occurs.
How It Works (Under the Hood)
A context manager is just a Python class with two special methods:
__enter__: Runs when you enter thewithblock.__exit__: Runs when you leave thewithblock.
Method 1: The Class-Based Approach (For Control)
Let’s build a Timer that measures code performance. We will update it so you can access the data afterwards.
import time
class Timer:
def __enter__(self):
self.start = time.time()
return self # This returns the instance to 'as t'
# The arguments here capture any error that happened inside the block
def __exit__(self, exc_type, exc_val, exc_tb):
self.end = time.time()
self.duration = self.end - self.start
# If an error occurred, we can log it here
if exc_type:
print(f"An error occurred: {exc_val}")
# Return False to let the error propagate (crash), True to suppress it
return False
# Using it
with Timer() as t:
for i in range(1000000):
pass
print(f"That took {t.duration:.4f} seconds.")Method 2: The “Lazy” Way (For Speed)
Writing a whole class just to time a script is verbose. Python’s contextlib module lets you use a generator to do the same thing in half the lines.
Everything before the yield is the setup (__enter__), and everything after is the teardown (__exit__).
from contextlib import contextmanager
import time
@contextmanager
def simple_timer():
start = time.time()
try:
yield # Control is handed to the 'with' block here
finally:
# This runs after the block finishes
end = time.time()
print(f"Elapsed time: {end - start:.4f} seconds")
# Usage
with simple_timer():
time.sleep(1)Which one should you use?
- Use the Class approach when you need to manage complex state or handle specific exceptions logically.
- Use the
@contextmanagerdecorator for simple resource management (like timers, temporary file changes, or locks).





