3 min read · 512 words
Python’s type hinting system has evolved from a quiet PEP into an indispensable tool for growing codebases. If you are still writing raw Python without type hints, you are missing out on instant IDE feedback, bulletproof refactoring, and self-documenting code.
Let's cut the theory. Here is a practical, no-fluff cheat sheet for the type hints you will actually use in production every day.
1. Primitives and Collections
Keep it simple. Scalars use standard Python types (str, int, float, bool), while collections require imports from the typing module or modern built-in generics (supported in Python 3.9+).
from typing import Dict, List, Optional, Tuple, Union # Scalars name: str = "Claude" age: int = 5 is_active: bool = True price: float = 99.9 # Collections tags: List[str] = ["ai", "llm", "python"] coordinates: Tuple[float, float] = (37.7749, -122.4194) metadata: Dict[str, Union[str, int]] = {"model": "claude-opus-5", "tokens": 1000000} # Optional values (equivalent to Type | None) middle_name: Optional[str] = None 2. Functions, Callables, and Generics
Functions are where type hints shine brightest. Always type your arguments and your return values. Use -> None for functions that perform side effects without returning data.
from typing import Callable, Sequence def calculate_cost(tokens: int, rate_per_million: float) -> float: """Calculate API cost based on token usage.""" return (tokens / 1_000_000) * rate_per_million def process_batch(items: Sequence[str], callback: Callable[[str], None]) -> None: """Process a sequence of items and execute a callback on each.""" for item in items: callback(item) # Real-world usage with the modern Anthropic SDK import anthropic def generate_completion(prompt: str, model_id: str = "claude-opus-5") -> str: client = anthropic.Anthropic() response = client.messages.create( model=model_id, max_tokens=4000, messages=[{"role": "user", "content": prompt}], ) # Extract text from the response content blocks safely return "".join(block.text for block in response.content if block.type == "text") 3. Advanced Patterns: Protocols and TypedDict
When your codebase grows, strict class hierarchies can slow you down. Use Protocol for structural subtyping (duck typing with type safety) and TypedDict for structured JSON payloads.
from typing import Protocol, TypedDict # Protocol: Any object with a 'generate' method matches this type class LLMProvider(Protocol): def generate(self, prompt: str) -> str: ... # TypedDict: Perfect for validating API payloads and config dictionaries class APIConfig(TypedDict): model: str max_tokens: int thinking_enabled: bool def configure_request(config: APIConfig) -> None: print(f"Configuring request for model: {config['model']}") # Valid dictionary matching TypedDict structure my_config: APIConfig = { "model": "claude-sonnet-5", "max_tokens": 8000, "thinking_enabled": True } configure_request(my_config) Pro-Tips for Daily Development
- Run MyPy strictly: Add
mypy --strict your_module.pyto your CI/CD pipeline to catch type errors before they hit production. - Embrace modern defaults: Prefer
list[str]overList[str]andstr | NoneoverOptional[str]if you are running Python 3.10+. - Don't over-type: If typing dynamic data becomes a battle against the type checker, use
Anytemporarily—just don't leave it there permanently.
Call to Action
What is your favorite advanced typing pattern in Python? Drop a comment below with your go-to trick or headache. If you found this cheat sheet helpful, hit the bookmark button and share it with your team!