Python Best Practices
Follows type-first, functional, and error handling patterns from CLAUDE.md. This skill covers language-specific idioms only.
Make Illegal States Unrepresentable
Use Python's type system to prevent invalid states at type-check time.
Frozen dataclasses for immutable domain models:
pythonfrom dataclasses import dataclass from datetime import datetime @dataclass(frozen=True) class User: id: str email: str name: str created_at: datetime # Frozen dataclasses are immutable — no accidental mutation
Discriminated unions with Literal:
pythonfrom dataclasses import dataclass from typing import Literal @dataclass class Success: status: Literal["success"] = "success" data: str @dataclass class Failure: status: Literal["error"] = "error" error: Exception RequestState = Success | Failure def handle_state(state: RequestState) -> None: match state: case Success(data=data): render(data) case Failure(error=err): show_error(err)
NewType for domain primitives:
pythonfrom typing import NewType UserId = NewType("UserId", str) OrderId = NewType("OrderId", str) def get_user(user_id: UserId) -> User: # Type checker prevents passing OrderId here ...
Protocol for structural typing:
pythonfrom typing import Protocol class Readable(Protocol): def read(self, n: int = -1) -> bytes: ... def process_input(source: Readable) -> bytes: # Accepts any object with a read() method — no inheritance required return source.read()
Python-Specific Error Handling
Chain exceptions with from err to preserve the original traceback:
pythontry: data = json.loads(raw) except json.JSONDecodeError as err: raise ValueError(f"invalid JSON payload: {err}") from err
Structured Logging
Use a module-level logger with %s formatting (deferred string interpolation):
pythonimport logging logger = logging.getLogger("myapp.widgets") def create_widget(name: str) -> Widget: logger.debug("creating widget: %s", name) widget = Widget(name=name) logger.debug("created widget id=%s", widget.id) return widget
Optional: ty
For fast type checking, consider ty from Astral (creators of ruff and uv). Written in Rust, significantly faster than mypy or pyright.
bashuvx ty check # run directly, no install needed uvx ty check src/ # check specific path
toml# pyproject.toml [tool.ty] python-version = "3.12"
When to choose:
ty— fastest, good for CI and large codebases (early stage, rapidly evolving)pyright— most complete type inference, VS Code integrationmypy— mature, extensive plugin ecosystem

