python-best-practices
💡 Summary
A comprehensive guide and enforcer for modern Python type-first development patterns, including dataclasses, unions, and protocols.
🎯 Target Audience
🤖 AI Roast: “It's a great style guide, but calling it a 'skill' is like selling a cookbook as a kitchen appliance.”
The README promotes loading configuration (e.g., API keys) from environment variables, which is a best practice. However, it does not explicitly warn against logging or serializing these secrets, which is a common risk. Mitigation: Ensure secret values are never printed, logged, or included in error messages.
name: python-best-practices description: Provides Python patterns for type-first development with dataclasses, discriminated unions, NewType, and Protocol. Must use when reading or writing Python files.
Python Best Practices
Type-First Development
Types define the contract before implementation. Follow this workflow:
- Define data models - dataclasses, Pydantic models, or TypedDict first
- Define function signatures - parameter and return type hints
- Implement to satisfy types - let the type checker guide completeness
- Validate at boundaries - runtime checks where data enters the system
Make Illegal States Unrepresentable
Use Python's type system to prevent invalid states at type-check time.
Dataclasses for structured data:
from dataclasses import dataclass from datetime import datetime @dataclass(frozen=True) class User: id: str email: str name: str created_at: datetime @dataclass(frozen=True) class CreateUser: email: str name: str # Frozen dataclasses are immutable - no accidental mutation
Discriminated unions with Literal:
from dataclasses import dataclass from typing import Literal @dataclass class Idle: status: Literal["idle"] = "idle" @dataclass class Loading: status: Literal["loading"] = "loading" @dataclass class Success: status: Literal["success"] = "success" data: str @dataclass class Failure: status: Literal["error"] = "error" error: Exception RequestState = Idle | Loading | Success | Failure def handle_state(state: RequestState) -> None: match state: case Idle(): pass case Loading(): show_spinner() 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 ... def create_user_id(raw: str) -> UserId: return UserId(raw)
Enums for constrained values:
from enum import Enum, auto class Role(Enum): ADMIN = auto() USER = auto() GUEST = auto() def check_permission(role: Role) -> bool: match role: case Role.ADMIN: return True case Role.USER: return limited_check() case Role.GUEST: return False # Type checker warns if case is missing
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 return source.read()
TypedDict for external data shapes:
from typing import TypedDict, Required, NotRequired class UserResponse(TypedDict): id: Required[str] email: Required[str] name: Required[str] avatar_url: NotRequired[str] def parse_user(data: dict) -> UserResponse: # Runtime validation needed - TypedDict is structural return UserResponse( id=data["id"], email=data["email"], name=data["name"], )
Module Structure
Prefer smaller, focused files: one class or closely related set of functions per module. Split when a file handles multiple concerns or exceeds ~300 lines. Use __init__.py to expose public API; keep implementation details in private modules (_internal.py). Colocate tests in tests/ mirroring the source structure.
Functional Patterns
- Use list/dict/set comprehensions and generator expressions over explicit loops.
- Prefer
@dataclass(frozen=True)for immutable data; avoid mutable default arguments. - Use
functools.partialfor partial application; compose small functions over large classes. - Avoid class-level mutable state; prefer pure functions that take inputs and return outputs.
Instructions
- Raise descriptive exceptions for unsupported cases; every code path returns a value or raises. This makes failures debuggable and prevents silent corruption.
- Propagate exceptions with context using
from err; catching requires re-raising or returning a meaningful result. Swallowed exceptions hide root causes. - Handle edge cases explicitly: empty inputs,
None, boundary values. Includeelseclauses in conditionals where appropriate. - Use context managers for I/O; prefer
pathliband explicit encodings. Resource leaks cause production issues. - Add or adjust unit tests when touching logic; prefer minimal repros that isolate the failure.
Examples
Explicit failure for unimplemented logic:
def build_widget(widget_type: str) -> Widget: raise NotImplementedError(f"build_widget not implemented for type: {widget_type}")
Propagate with context to preserve the original traceback:
try: data = json.loads(raw) except json.JSONDecodeError as err: raise ValueError(f"invalid JSON payload: {err}") from err
Exhaustive match with explicit default:
def process_status(status: str) -> str: match status: case "active": return "processing" case "inactive": return "skipped" case _: raise ValueError(f"unhandled status: {status}")
Debug-level tracing with namespaced logger:
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
Configuration
- Load config from environment variables at startup; validate required values before use. Missing config should fail immediately.
- Define a config dataclass or Pydantic model as single source of truth; avoid
os.getenvscattered throughout code. - Use sensible defaults for development; require explicit values for production secrets.
Examples
Typed config with dataclass:
import os from dataclasses import dataclass @dataclass(frozen=True) class Config: port: int = 3000 database_url: str = "" api_key: str = "" env: str = "development" @classmethod def from_env(cls) -> "Config": database_url = os.environ.get("DATABASE_URL", "") if not database_url: raise ValueError("DATABASE_URL is required") return cls( port=int(os.environ.get("PORT", "3000")), database_url=database_url, api_key=os.environ["API_KEY"], # required, will raise if missing env=os.environ.get("ENV", "development"), ) config = Config.from_env()
Optional: ty
For fast type checking, consider ty from Astral (creators of ruff and uv). Written in Rust, it's significantly faster than mypy or pyright.
Installation and usage:
# Run directly with uvx (no install needed) uvx ty check # Check specific files uvx ty check src/main.py # Install permanently uv tool install ty
Key features:
- Automatic virtual environment detection (via
VIRTUAL_ENVor.venv) - Project discovery from
pyproject.toml - Fast incremental checking
- Compatible with standard Python type hints
Configuration in pyproject.toml:
[tool.ty] python-version = "3.12"
When to use ty vs alternatives:
ty- fastest, good for CI and large codebases (early stage, rapidly evolving)pyright- most complete type inference, VS Code integrationmypy- mature, extensive plugin ecosystem
Pros
- Promotes robust, self-documenting code through type safety.
- Covers a wide range of modern Python typing features with clear examples.
- Emphasizes practical patterns like immutability and error handling.
Cons
- Primarily a knowledge base; lacks executable tools or automation.
- Assumes user already uses a type checker (mypy, pyright).
- May be overwhelming for beginners to Python typing.
Related Skills
pytorch
S“It's the Swiss Army knife of deep learning, but good luck figuring out which of the 47 installation methods is the one that won't break your system.”
agno
S“It promises to be the Kubernetes for agents, but let's see if developers have the patience to learn yet another orchestration layer.”
nuxt-skills
S“It's essentially a well-organized cheat sheet that turns your AI assistant into a Nuxt framework parrot.”
Disclaimer: This content is sourced from GitHub open source projects for display and rating purposes only.
Copyright belongs to the original author 0xBigBoss.
