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:
from 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:
from 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:
from 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:
from 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:
try: 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):
import 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.
uvx ty check # run directly, no install needed uvx ty check src/ # check specific path
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 integration
-
mypy — mature, extensive plugin ecosystem