Lab 14: Context Managers & Protocols
Objective
Time
Prerequisites
Tools
Lab Instructions
Step 1: Context Manager Protocol
docker run --rm zchencow/innozverse-python:latest python3 -c "
from contextlib import contextmanager, asynccontextmanager, suppress
import time, threading
# Class-based context manager
class Timer:
def __init__(self, name: str = ''):
self.name = name; self.elapsed = 0.0
def __enter__(self) -> Timer:
self._start = time.perf_counter()
return self
def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
self.elapsed = time.perf_counter() - self._start
label = f'[{self.name}] ' if self.name else ''
print(f'{label}Elapsed: {self.elapsed*1000:.2f}ms')
return False # don't suppress exceptions
with Timer('sort') as t:
data = sorted(range(10_000), reverse=True)
print(f'Timer accessible after: {t.elapsed*1000:.2f}ms')
# Generator-based context manager
@contextmanager
def transaction(conn_name: str):
print(f' BEGIN TRANSACTION [{conn_name}]')
try:
yield conn_name
print(f' COMMIT [{conn_name}]')
except Exception as e:
print(f' ROLLBACK [{conn_name}]: {e}')
raise
finally:
print(f' CONNECTION CLOSED [{conn_name}]')
print()
print('=== Successful transaction ===')
with transaction('db-main') as conn:
print(f' Executing on: {conn}')
print(f' INSERT INTO orders ...')
print()
print('=== Failed transaction ===')
try:
with transaction('db-replica') as conn:
print(f' UPDATE products ...')
raise ValueError('constraint violation')
except ValueError:
pass
# Nested context managers
print()
print('=== Nested CMs ===')
with Timer('outer'), Timer('inner-a'):
x = sum(range(100_000))
# suppress — silently ignore specific errors
print()
with suppress(FileNotFoundError):
open('/nonexistent/file.txt')
print('FileNotFoundError suppressed cleanly')
# Reentrant context manager
class BulkWriter:
def __init__(self, name: str):
self.name = name; self._depth = 0; self._buffer = []
def __enter__(self) -> BulkWriter:
self._depth += 1
if self._depth == 1: print(f' [{self.name}] Opened')
return self
def __exit__(self, *args) -> bool:
self._depth -= 1
if self._depth == 0:
print(f' [{self.name}] Flushed {len(self._buffer)} items')
return False
def write(self, item: str) -> None:
self._buffer.append(item)
with BulkWriter('products') as w:
w.write('Surface Pro')
with w: # reentrant — same object
w.write('Surface Pen')
w.write('Office 365')
"Step 2: Iterator Protocol & Custom Iterables
Steps 3–8: Descriptor Protocol, __slots__, Comparison Operators, __repr__, __hash__, Capstone
__slots__, Comparison Operators, __repr__, __hash__, CapstoneSummary
Dunder
Protocol
When to use
Further Reading
Last updated
