Advanced Python Tips Every AI Developer Should Know
Handy Python techniques and lesser-known features that will make you a more efficient developer.
Handy Python techniques and lesser-known features that make you a more efficient AI engineer. These are patterns I use daily in production GenAI codebases.
1. Dataclasses with slots for Hot Paths
from dataclasses import dataclass
@dataclass(slots=True, frozen=True)
class Chunk:
doc_id: str
text: str
score: floatslots=True cuts memory usage significantly and speeds up attribute access — noticeable when you hold millions of chunks. frozen=True makes instances hashable and prevents accidental mutation.
2. functools.lru_cache for Expensive Pure Calls
from functools import lru_cache
@lru_cache(maxsize=4096)
def embed_query(text: str) -> tuple[float, ...]:
return tuple(model.encode(text))Return tuples (hashable) rather than lists when caching. For async code, use async_lru or cache at the service layer.
3. Generators for Streaming Pipelines
Document ingestion should never load everything in memory:
def iter_chunks(paths):
for path in paths:
for page in parse_pages(path):
yield from split_page(page)Compose generators end-to-end and your pipeline handles a 10-page or 10-million-page corpus with the same memory footprint.
4. itertools.batched (3.12+)
from itertools import batched
for batch in batched(chunks, 64):
embeddings = model.encode([c.text for c in batch])The clean way to batch API calls — no manual index math.
5. Structural Pattern Matching for Agent Routing
match event:
case {"type": "tool_call", "name": name, "args": args}:
result = dispatch_tool(name, args)
case {"type": "final", "content": content}:
return content
case _:
raise ValueError(f"Unexpected event: {event}")Far more readable than nested if isinstance chains when handling LLM output events.
6. contextvars for Request-Scoped Tracing
import contextvars
request_id = contextvars.ContextVar("request_id", default="-")Set it once per request; every log line and downstream call can read it without threading parameters through your whole call stack — works correctly with asyncio.
7. Pydantic for LLM Output Validation
class Verdict(BaseModel):
is_supported: bool
confidence: float = Field(ge=0, le=1)
citations: list[str]
verdict = Verdict.model_validate_json(llm_response)Never trust raw model output. Validate at the boundary, retry with the validation error appended to the prompt.
8. typing.Protocol Over Inheritance
Define what a retriever does, not what it is:
class Retriever(Protocol):
def search(self, query: str, k: int) -> list[Chunk]: ...Any class with a matching search method satisfies the protocol — perfect for swapping dense/sparse/hybrid implementations in tests.
Master these and your Python reads like infrastructure, not scripts.