Module 6 ยท Lesson 20

Context Managers

๐Ÿ Pythonโฑ 11 min read๐Ÿ“– Core Concept

Context Managers: Resource Management

Context managers handle setup and cleanup automatically. The with statement calls __enter__ on entry and __exit__ on exit โ€” even if an exception occurs. You've already used them with files.

# File I/O is the classic context manager with open("file.txt", "w") as f: f.write("content") # File is automatically closed here, even if an exception occurred # What it's equivalent to: f = open("file.txt", "w") try: f.write("content") finally: f.close() # Multiple context managers in one with statement with open("input.txt") as fin, open("output.txt", "w") as fout: for line in fin: fout.write(line.upper())

Building Context Managers with Classes

class Timer: """Time a block of code.""" import time as _time def __enter__(self): self.start = __import__('time').perf_counter() return self # Bound to 'as' variable def __exit__(self, exc_type, exc_val, exc_tb): self.elapsed = __import__('time').perf_counter() - self.start print(f"Elapsed: {self.elapsed:.4f}s") return False # False = don't suppress exceptions with Timer() as t: # Do some work sum(range(1_000_000)) # Elapsed: 0.0321s # Database connection example class DatabaseConnection: def __init__(self, db_url): self.db_url = db_url self.conn = None def __enter__(self): self.conn = connect(self.db_url) return self.conn def __exit__(self, exc_type, exc_val, exc_tb): if exc_type: self.conn.rollback() # Error: roll back else: self.conn.commit() # Success: commit self.conn.close() return False # Let exceptions propagate with DatabaseConnection("sqlite:///mydb.db") as db: db.execute("INSERT INTO users VALUES (?)", ("Alice",))

contextlib: Easier Context Managers

from contextlib import contextmanager # Use a generator instead of a class @contextmanager def timer(): import time start = time.perf_counter() yield # Code in 'with' block runs here elapsed = time.perf_counter() - start print(f"Elapsed: {elapsed:.4f}s") with timer(): sum(range(1_000_000)) # Temporary directory @contextmanager def temp_directory(): import tempfile, shutil tmpdir = tempfile.mkdtemp() try: yield tmpdir finally: shutil.rmtree(tmpdir) # Always clean up with temp_directory() as tmpdir: # Work in tmpdir โ€” it's deleted after the block with open(f"{tmpdir}/test.txt", "w") as f: f.write("temporary data") # contextlib.suppress โ€” suppress specific exceptions from contextlib import suppress with suppress(FileNotFoundError): os.remove("might_not_exist.txt") # No error if missing

Key Takeaways

Practice Exercises

  1. Write a @contextmanager called redirect_stdout that captures print output to a string.
  2. Write a context manager that changes the working directory and restores it afterwards.
  3. Write a context manager for a "transaction": on exit, call commit() if no exception, rollback() if there was one.
โ† Exceptions