Home Blog Advanced Python Tips Every AI Developer Should Know
Python

Advanced Python Tips Every AI Developer Should Know

Handy Python techniques and lesser-known features that will make you a more efficient developer.

Purnendu Das Purnendu Das AI Engineer & Open Source Builder January 28, 2025 7 min read PythonTips

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

python
from dataclasses import dataclass

@dataclass(slots=True, frozen=True)
class Chunk:
    doc_id: str
    text: str
    score: float

slots=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

python
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:

python
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+)

python
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

python
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

python
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

python
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:

python
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.