Language-specific patterns, anti-patterns, and best practices for writing Python code.
| Resource | When to Use |
|---|---|
| quality.md | Naming, comments, docstrings, anti-patterns |
| composition.md | Classes vs functions, protocols, inheritance |
| control-flow.md | Pattern matching, iteration vs recursion |
| domain-types.md | Dataclasses, Pydantic, discriminated unions |
| errors.md | Exceptions vs Result types, error propagation |
| modules.md | Package structure, imports, public APIs |
| async-io.md | Async/await, TaskGroups, timeouts, blocking I/O |
| test.md | Testing patterns and practices |
Spend 5x more time finding good names than writing comments.
Comments explain WHY, not WHAT. The code should be self-documenting through clear naming.
NEVER use inheritance for code reuse. ONLY for exception hierarchies.
This rule is absolute. Use composition, protocols, and module functions instead.
@staticmethod is ALWAYS wrong. Use module-level functions.
Python has namespaces: modules. This is not Java.
Create classes ONLY when you need mutable state encapsulated across multiple method calls.
Otherwise use module functions. Classes are not namespaces.
NEVER use flat layout. ALWAYS use src/myproject/ structure.
Ensures tests run against installed package, catches packaging issues early.
Organize by domain feature (chat/, storage/), NOT technical layer (models/, services/).
Keep related types and functions together in cohesive modules.
ALWAYS model your domain with types FIRST.
- Types ARE the design, not documentation
- Use
@dataclass(frozen=True, kw_only=True)for value objects - Make illegal states unrepresentable through discriminated unions
- Parse at boundaries, NEVER pass
dict[str, Any]into core logic
Accept permissive input at boundaries, immediately parse to strict canonical types internally.
Core logic MUST work with guaranteed-valid, typed data only.
USE exceptions by default. Result types ONLY for:
- Async task coordination (collect all results without canceling)
- Batching validation errors (show user all errors at once)
Catch at application boundaries. Propagate in core logic.
Validate YOUR invariants, not theirs.
Don't duplicate validation that dependencies already do.
Never use system Python. Always use uv for package management and execution.
| Tool | Purpose |
|---|---|
uv |
Package management, virtual environments, script execution |
pyright |
Type checking |
ruff |
Linting and formatting |
Self-contained scripts: Use PEP 723 inline metadata with uv run:
# /// script
# requires-python = ">=3.12"
# dependencies = ["httpx", "rich"]
# ///Flag these when writing or reviewing Python code:
Structure:
- ✘ Behavioral inheritance (except exceptions)
- ✘ @staticmethod (use module functions)
- ✘ Classes as namespaces (use modules)
- ✘ Utils/common modules (use specific names)
- ✘ Layer-based organization (models/, services/)
Types:
- ✘ dict[str, Any] deep in logic (parse at boundaries)
- ✘ Positional pattern matching (use keyword patterns)
- ✘ Mutable default arguments
Async:
- ✘ Blocking I/O in async context
- ✘ Missing timeouts on async I/O
- ✘ asyncio.gather without proper error handling
Style:
- ✘ Comments restating code
- ✘ Section divider comments (split into modules)
- ✘ Global mutable state
- ✘ Multiple unrelated classes in one module
Use type hints consistently:
- Always annotate function signatures
- Use
from __future__ import annotationsfor forward references - Prefer protocols over abstract base classes
- Use TypedDict or dataclasses over dict[str, Any]
- quality.md, composition.md, domain-types.md, etc.
- code-review: Review methodology
- code-test: Test-driven development workflow
- PEP 8 - Style Guide for Python Code
- PEP 20 - The Zen of Python
- Google Python Style Guide
- Type Hints Cheat Sheet