Advanced Python
The features that separate scripting from engineering: iterators, generators, decorators, context managers, and async.
1. The Data Model & Dunder Methods
Python's behavior is driven by __dunder__ methods. Implementing them lets your objects behave like built-ins — iterable, indexable, callable, comparable.
class Vector:
def __init__(self, *components): self.c = components
def __len__(self): return len(self.c)
def __getitem__(self, i): return self.c[i]
def __add__(self, other): return Vector(*(a+b for a,b in zip(self.c, other.c)))
def __repr__(self): return f"Vector{self.c}"
v = Vector(1, 2, 3) + Vector(4, 5, 6) # Vector(5, 7, 9)
print(len(v), v[0]) # 3 52. Iterators & Generators
Generators produce values lazily — perfect for huge files and infinite streams. They use constant memory regardless of input size.
def read_jsonl(path):
with open(path) as f:
for line in f: # lazy: one line at a time
yield json.loads(line)
# Process 50 GB without loading it into RAM
for record in read_jsonl("events.jsonl"):
process(record)Generator expressions are the lazy cousin of list comprehensions:
total = sum(x*x for x in range(10_000_000)) # no list built3. Decorators
A decorator is a function that wraps another function. Used for caching, timing, auth, retries, logging — everywhere.
import time, functools
def timed(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
t0 = time.perf_counter()
out = fn(*args, **kwargs)
print(f"{fn.__name__} took {time.perf_counter()-t0:.3f}s")
return out
return wrapper
@timed
def train_model(X, y): ...4. Context Managers
with blocks guarantee setup/teardown — even on exceptions. Build your own with contextlib:
from contextlib import contextmanager
@contextmanager
def timer(label):
t0 = time.perf_counter()
try: yield
finally: print(f"{label}: {time.perf_counter()-t0:.3f}s")
with timer("inference"):
preds = model.predict(X)5. Async / Await
Async lets a single thread juggle thousands of I/O-bound tasks (HTTP calls, DB queries). It does not speed up CPU-bound work.
import asyncio, aiohttp
async def fetch(session, url):
async with session.get(url) as r:
return await r.json()
async def main(urls):
async with aiohttp.ClientSession() as s:
return await asyncio.gather(*(fetch(s, u) for u in urls))
results = asyncio.run(main(urls)) # 1000 calls in parallel6. Performance Tools
functools.lru_cache— memoize pure functionsdataclasses— boilerplate-free value classes__slots__— cut per-instance memory by ~40%collections.deque / Counter / defaultdict— O(1) primitivesitertools— chain, groupby, islice, product
async for I/O, multiprocessing for CPU. Reach for a decorator before copy-pasting wrapper code.