Python Patterns
Modern Python 3.10+ patterns and best practices.
Type Hints
Strict Typing
from typing import TypeVar, Protocol, Optional
from collections.abc import Sequence
T = TypeVar('T')
def first_or_none(items: Sequence[T]) -> Optional[T]:
return items[0] if items else None
Protocols (Structural Typing)
from typing import Protocol, runtime_checkable
@runtime_checkable
class Renderable(Protocol):
def render(self) -> str: ...
class HtmlWidget:
def render(self) -> str:
return "<div>widget</div>"
def display(item: Renderable) -> None:
print(item.render())
display(HtmlWidget()) # Works - structural match
TypedDict for Structured Data
from typing import TypedDict, NotRequired
class UserConfig(TypedDict):
name: str
email: str
theme: NotRequired[str] # Optional key
Dataclasses and Immutability
Frozen Dataclasses
from dataclasses import dataclass, field
@dataclass(frozen=True, slots=True)
class Point:
x: float
y: float
def distance_to(self, other: "Point") -> float:
return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5
Pydantic for Validation
from pydantic import BaseModel, Field, field_validator
class CreateUser(BaseModel):
name: str = Field(min_length=1, max_length=100)
email: str
age: int = Field(ge=0, le=150)
@field_validator('email')
@classmethod
def validate_email(cls, v: str) -> str:
if '@' not in v:
raise ValueError('Invalid email')
return v.lower()
Async Patterns
Structured Concurrency
import asyncio
async def fetch_all(urls: list[str]) -> list[str]:
async with asyncio.TaskGroup() as tg:
tasks = [tg.create_task(fetch(url)) for url in urls]
return [t.result() for t in tasks]
Async Context Managers
from contextlib import asynccontextmanager
from typing import AsyncIterator
@asynccontextmanager
async def db_transaction() -> AsyncIterator[Connection]:
conn = await get_connection()
try:
yield conn
await conn.commit()
except Exception:
await conn.rollback()
raise
finally:
await conn.close()
Error Handling
Result Pattern
from dataclasses import dataclass
from typing import Generic, TypeVar, Union
T = TypeVar('T')
E = TypeVar('E', bound=Exception)
@dataclass(frozen=True)
class Ok(Generic[T]):
value: T
@dataclass(frozen=True)
class Err(Generic[E]):
error: E
Result = Union[Ok[T], Err[E]]
def divide(a: float, b: float) -> Result[float, ValueError]:
if b == 0:
return Err(ValueError("Division by zero"))
return Ok(a / b)
Match Statements (3.10+)
match command.split():
case ["quit"]:
sys.exit(0)
case ["go", direction]:
move(direction)
case ["get", item] if item in inventory:
pick_up(item)
case _:
print(f"Unknown command: {command}")
Tooling
| Tool | Purpose | Command |
|---|---|---|
| uv | Package management | uv add package |
| ruff | Linting + formatting | ruff check . && ruff format . |
| mypy | Type checking | mypy src/ --strict |
| pytest | Testing | pytest -xvs |
| pyright | Type checking (fast) | pyright src/ |
Checklist
- Type hints on all public functions
- Dataclasses for data containers (not plain dicts)
- Async for I/O-bound operations
- Match statements for complex branching (3.10+)
- Protocols for duck typing
- Pydantic for input validation
- uv for package management
- ruff for linting and formatting
