git:20251203.c15c4d0 to git:20260301.390d514

265 added, 629 removed. Audit A to A.

---
name: python-programmer
description: Python-specific idioms, philosophy, and expert-level patterns. Use when working with Python code, including Jupyter notebooks (.ipynb). Covers Pythonic thinking, common pitfalls from other language backgrounds, testing ecosystem navigation, type hints trade-offs, and when to use modern Python features.
---
# Python Programmer
<skill_scope skill="python-programmer">
This skill provides guidance on Python-specific idioms, philosophy, and expert-level judgment calls. Python's design emphasizes readability and "one obvious way" to do things, but achieving truly Pythonic code requires understanding when and why to use Python's idioms.
**Related skills:**
- `software-engineer` — Core engineering philosophy, system design principles
- `functional-programmer` — When functional approaches are clearer
- `test-driven-development` — Testing philosophy and TDD principles
</skill_scope>
## When to Use This Skill
<when_to_use>
Use this skill when:
- Working with Python code
- Deciding when Python is the right tool for a problem
- Navigating between Python's "obvious ways" and edge cases
- Choosing between testing frameworks, type systems, or async patterns
- Avoiding anti-patterns from Java, C, or JavaScript backgrounds
- Making trade-offs between Pythonic idioms and readability
</when_to_use>
- <core_philosophy>
## Core Philosophy
+ <core_philosophy>
**For foundational software engineering principles, see the software-engineer skill.**
### The Zen of Python (PEP 20)
- Python's design philosophy is captured in "The Zen of Python" (import this to see it). Key principles that guide Pythonic code:
+ Python's design philosophy is captured in "The Zen of Python" (`import this`). Key principles:
**Quote to remember:** "Explicit is better than implicit. Simple is better than complex. Readability counts." — Tim Peters, PEP 20
- **What this means in practice:**
+ **In practice:**
- Favor clarity over cleverness
- One obvious way beats multiple equivalent ways
- Code is read more than written (optimize for readers)
- Practicality beats purity (Python isn't a pure functional or OO language)
- **Staff insight:** The Zen is philosophy, not law. Sometimes implicit is fine (context managers hide `__enter__` and `__exit__`). Sometimes there are two ways (list comprehension vs `map`). The Zen guides judgment; it doesn't eliminate it.
+ **Staff insight:** The Zen is philosophy, not law. Sometimes implicit is fine (e.g., context managers hide `__enter__` and `__exit__`). Sometimes there are two ways (e.g., list comprehension vs `map`). The Zen guides judgment; it doesn't eliminate it.
<pythonic_vs_readable>
### When Pythonic Idioms Hurt Readability
The Zen says both "Explicit is better than implicit" and to use Python idioms. When they conflict, optimize for readers.
| Code Characteristic | Use Pythonic Idiom | Use Explicit Form |
|---------------------|-------------------|-------------------|
| Reader must pause to parse | No | Yes |
| Requires advanced feature knowledge | No | Yes |
| In critical path / main logic | No | Yes |
| In isolated utility function | Yes | Maybe |
| Junior engineer would need to look it up | No | Yes |
| Saves 1-2 lines at cost of clarity | No | Yes |
- | Standard pattern (simple dict comprehension) | Yes | No |
- | Clever trick (tuple sort keys, walrus operator chains) | No | Yes |
+ | Standard pattern (e.g., simple dict comprehension) | Yes | No |
+ | Clever trick (e.g., tuple sort keys, walrus operator chains) | No | Yes |
**Heuristics:**
- If you need a comment explaining the trick, the trick is too clever
- Nested comprehensions beyond 2 levels need explicit loops
- - One-letter variables acceptable only in comprehensions under 10 lines
- - Tuple sort keys are clever unless the tuple structure is obvious
- Walrus operator (`:=`) in comprehension conditions is usually too clever
- - Code golf is not a virtue—readable beats concise
-
- **Staff insight:** Pythonic doesn't mean cryptic. The goal is code that Python programmers read at a glance, not code that demonstrates language mastery. When an idiom requires mental parsing, you've crossed from idiomatic to showing off. Write code for the maintainer, not the interpreter.
+ - Code golf is not a virtue — readable beats concise
</pythonic_vs_readable>
<eafp_principle>
- ### EAFP: Easier to Ask Forgiveness than Permission
-
- Python culture prefers trying operations and handling exceptions over checking conditions first.
-
- **EAFP (Pythonic):**
- ```python
- try:
- value = my_dict[key]
- except KeyError:
- value = default
- ```
-
- **LBYL (Look Before You Leap - unpythonic):**
- ```python
- if key in my_dict:
- value = my_dict[key]
- else:
- value = default
- ```
-
- **When EAFP wins:**
- - Operations that might fail (file access, network calls, dict lookups)
- - Race conditions matter (checking then acting creates gaps)
- - Exceptional cases are rare (exceptions aren't expensive in Python)
- - Code reads cleaner without defensive checks
+ ### EAFP vs LBYL
- **When LBYL is acceptable:**
- - Pre-flight validation before expensive operations
- - Control flow where exceptions obscure logic
- - Performance-critical tight loops (check once, execute many)
+ Python culture prefers EAFP (i.e., try/except) over LBYL (i.e., check-then-act). Use EAFP for dict lookups, file access, network calls, and anywhere race conditions matter. Use LBYL for pre-flight validation before expensive operations and performance-critical tight loops where the exceptional case is common.
- **Staff insight:** EAFP isn't about exceptions being "free" — it's about correctness and clarity. The file existence check has a race condition; the exception handling doesn't. But don't abuse EAFP for control flow in loops.
+ **Staff insight:** EAFP is about correctness and clarity, not performance. The file existence check has a race condition; the exception handling doesn't. But exceptions have real cost (e.g., stack unwinding, traceback construction) — they're fine when the exceptional case is rare, not for expected control flow in loops.
</eafp_principle>
<duck_typing>
- ### Duck Typing Over Type Checking
-
- "If it walks like a duck and quacks like a duck, it's a duck." Python prefers protocols (behavior) over explicit types.
-
- **Duck typing:**
- - Accept any object that supports needed operations
- - Don't check types explicitly (isinstance is a code smell, usually)
- - Design for protocols, not inheritance hierarchies
-
- **When duck typing works:**
- - Functions accepting "file-like objects" (read, write, close)
- - Iterables (anything supporting `__iter__`)
- - Mappings (anything supporting `__getitem__`)
-
- **When explicit types help:**
- - Type hints for documentation and IDE support
- - `isinstance` with abstract base classes (collections.abc)
- - Validating user input or external data
+ ### Duck Typing and Protocols
- **Staff insight:** Type hints and duck typing coexist. Use protocols (`typing.Protocol`) for duck-typed interfaces, not concrete types. Type hints document expectations; duck typing provides flexibility.
+ Prefer protocols (i.e., behavior) over explicit type checking. Don't use `isinstance` except with abstract base classes at system boundaries. Use `typing.Protocol` for duck-typed interfaces — it gives you structural subtyping with type checker support. Use `@runtime_checkable` only when you genuinely need runtime protocol checking.
</duck_typing>
</core_philosophy>
- <fundamental_principles>
+ ## Safety Constraints
+
+ <safety_constraints>
+ - **NEVER** use mutable default arguments (e.g., lists, dicts, sets) without the `None` sentinel pattern
+ - **NEVER** use `global` for shared state — use classes or explicit parameter passing
+ - **NEVER** catch bare `Exception` or bare `except:` and swallow errors silently
+ - **NEVER** use `eval()` or `exec()` on untrusted input
+ - **NEVER** sacrifice readability for cleverness — a 4-line loop beats a cryptic 1-line comprehension
+ - **NEVER** recommend or configure darglint — it was archived December 2022 and receives no maintenance; use pydoclint
+ - **NEVER** generate `requirements.txt` or `setup.py` for new projects — use `pyproject.toml` with uv
+ - **NEVER** use `typing.Optional[X]` when targeting Python 3.10+ — use `X | None` instead
+ - **NEVER** use bare `except:` without an exception type
+ - **NEVER** use `from __future__ import annotations` in new code — it's superseded by PEP 649 (i.e., deferred evaluation, default in 3.14)
+ - **ALWAYS** use context managers (`with`) for file handles, locks, and database connections
+ - **ALWAYS** use parameterized queries — never string concatenation for SQL
+ - **ALWAYS** validate and sanitize untrusted input at system boundaries
+ - **ALWAYS** include `if __name__ == "__main__":` guard in executable scripts
+ - **ALWAYS** follow existing project conventions for docstring style, tooling, and package management — don't fight established codebases
+ </safety_constraints>
+
## Fundamental Principles
+ <fundamental_principles>
<comprehensions>
### Comprehensions: Simple Cases Only
- List/dict/set comprehensions are Pythonic for *simple* transformations. Complexity thresholds matter.
-
- **When comprehensions win:**
- - Single transformation (`[x*2 for x in numbers]`)
- - Single filter (`[x for x in items if x > 0]`)
- - Transformation + filter (`[x.name for x in users if x.active]`)
-
- **When to use explicit loops:**
- - More than one level of nesting (`[[... for y in x] for x in items]` — borderline)
- - Two or more conditions in the filter
- - Any logic requiring explanation
- - Side effects (comprehensions shouldn't have side effects)
- - Early termination needed
+ List/dict/set comprehensions are Pythonic for *simple* transformations.
- **Comprehension complexity limits:**
+ **Complexity limits:**
- One `for` clause: usually fine
- Two `for` clauses: acceptable for obvious Cartesian products
- Three+ `for` clauses: use explicit loops
- Walrus operator (`:=`) in conditions: almost always too clever
+ - More than one filter condition: use explicit loops
+ - Any logic requiring explanation: use explicit loops
+ - Side effects: never use comprehensions
- **Staff insight:** Comprehensions are readable when they fit on one line and scan left-to-right. The moment you nest, chain conditions, or use walrus operators, you're optimizing for concision over clarity. A 4-line explicit loop is better than a 1-line comprehension that requires careful reading.
+ **Staff insight:** Comprehensions are readable when they fit on one line and scan left-to-right. The moment you nest, chain conditions, or use walrus operators, you're optimizing for concision over clarity.
</comprehensions>
<context_managers>
- ### Context Managers for Resource Management
-
- The `with` statement ensures cleanup happens. Always use it for files, locks, database connections.
-
- **Why context managers matter:**
- - Guarantee cleanup even with exceptions
- - Make resource lifetime explicit
- - Prevent resource leaks
-
- **When to create context managers:**
- - Managing paired operations (acquire/release, open/close)
- - Temporary state changes (changing directory, mocking)
- - Transactions (begin/commit/rollback)
+ ### Context Managers
- **Staff insight:** The `contextlib` module provides helpers: `contextmanager` decorator for simple cases, `ExitStack` for dynamic resource management. Don't write try/finally when a context manager expresses intent better.
+ Always use `with` for files, locks, and database connections. Use `contextlib.contextmanager` for simple cases, `ExitStack` for dynamic resource management. Don't write try/finally when a context manager expresses intent better.
</context_managers>
<iterators_generators>
- ### Iterators and Generators Over Materialized Lists
-
- Python's iterators are lazy by design. Use them to avoid unnecessary memory allocation.
-
- **When generators win:**
- - Large or infinite sequences
- - One-pass iteration suffices
- - Composing transformations (map/filter chains)
- - Memory matters more than random access
-
- **When lists are needed:**
- - Multiple passes over data
- - Random access required
- - Length needed upfront
- - Debugging (generators can't be inspected without consuming)
+ ### Iterators and Generators
- **Staff insight:** Generator expressions `(x for x in items)` are like comprehensions but lazy. Use them in function calls that consume iterables: `sum(x**2 for x in numbers)` doesn't build a list. But don't cargo-cult generators — lists are fine for small data.
+ Use generators for large/infinite sequences and one-pass iteration. Use lists when you need multiple passes, random access, or length upfront. Generator expressions in function calls avoid intermediate lists: `sum(x**2 for x in numbers)`.
</iterators_generators>
<mutable_defaults>
- ### Mutable Default Arguments Are Dangerous
-
- Default arguments are evaluated once at function definition, not each call. Mutable defaults (lists, dicts) persist across calls.
-
- **The classic footgun:**
- ```python
- def append_to(element, to=[]): # BUG: list persists across calls
- to.append(element)
- return to
+ ### Mutable Default Arguments
- append_to(1) # [1]
- append_to(2) # [1, 2] - NOT [2]!
- ```
+ Default arguments are evaluated once at function definition. Use `None` as a sentinel for mutable defaults:
- **The fix:**
```python
def append_to(element, to=None):
if to is None:
to = []
to.append(element)
return to
```
-
- **When this matters:**
- - Any mutable default (list, dict, set, custom objects)
- - Class methods with default arguments
- - Cached computation in default arguments (evaluated at import time)
-
- **Staff insight:** This isn't a bug — it's how Python works. Defaults are values, not expressions. Use `None` as a sentinel, or document the sharing behavior if it's intentional (rare).
</mutable_defaults>
</fundamental_principles>
- <when_python_works>
- ## When Python Works Well
-
- Python excels in specific problem domains. Recognize when Python's strengths align with your needs.
-
- **Rapid prototyping and iteration:**
- - Fast development cycle matters more than runtime performance
- - Requirements are evolving
- - Exploratory programming (data science, research)
-
- **Scripting and automation:**
- - Glue code between systems
- - System administration tasks
- - Build and deployment scripts
- - Data processing pipelines
-
- **Data analysis and scientific computing:**
- - Rich ecosystem (NumPy, pandas, scikit-learn)
- - Jupyter notebooks for interactive exploration
- - Visualization libraries (matplotlib, seaborn)
- - Integration with C/Fortran for performance
-
- **Web services and APIs:**
- - Django/Flask for rapid API development
- - FastAPI for modern async APIs with type hints
- - Mature ecosystem (ORMs, auth, testing)
- - Good enough performance for most services
-
- **Education and accessibility:**
- - Readable syntax lowers entry barrier
- - Interactive REPL for experimentation
- - Extensive documentation and community
- </when_python_works>
-
- <when_python_struggles>
- ## When Python Struggles
-
- **Performance-critical computation:**
- - Tight loops over large data (use NumPy or drop to C/Rust)
- - Real-time systems with latency requirements
- - Video/audio processing, graphics, games
- - High-throughput services (consider Go, Java, Rust)
-
- **Mobile development:**
- - No first-class mobile platform support
- - Kivy/BeeWare exist but aren't mainstream
- - Battery impact of interpreted language
- - Distribution and packaging challenges
-
- **Systems programming:**
- - Low-level hardware access
- - Operating system components
- - Device drivers
- - Memory layout control needed
-
- **Parallel computation:**
- - GIL (Global Interpreter Lock) prevents true parallelism for CPU-bound tasks
- - Use multiprocessing (expensive process creation) or drop to C
- - Async/await helps with I/O-bound, not CPU-bound
+ ## Type System
- **Large-scale applications with many developers:**
- - Dynamic typing can hinder refactoring at scale
- - Type hints help but aren't enforced at runtime
- - Consider statically-typed languages (Java, C#, TypeScript) for very large teams
+ <type_system>
+ ### Type Hints Are Mandatory
- **Staff insight:** Python's sweet spot is prototyping, scripting, data processing, and web services. Don't force it into low-level, high-performance, or mobile domains. Use Python where its strengths (development speed, ecosystem, readability) outweigh its weaknesses (performance, GIL, mobile).
- </when_python_struggles>
+ Type hints are required for all production code. They provide explicit, machine-readable contracts; enable static analysis; and are critical for LLM-assisted development.
- <staff_level_insights>
- ## Staff-Level Insights
+ **Where to use type hints:**
+ - All public APIs, function signatures, and class attributes: required
+ - Internal functions in non-trivial modules: required
+ - Local variables: only when type isn't obvious from context
+ - Throwaway scripts (i.e., < 50 lines, one-time use): optional
- ### Type Hints and Documentation Are Essential
+ <modern_type_syntax>
+ ### Modern Type Syntax
- Python 3.5+ supports type hints (PEP 484), and they're mandatory for quality code.
+ **Union types (3.10+):** Use `X | None` instead of `Optional[X]`. Use `int | str` instead of `Union[int, str]`.
- **Why type hints matter:**
- - Explicit, machine-readable contracts (unambiguous, can't drift from code)
- - Enable static analysis (mypy/pyright) to catch errors before runtime
- - **Critical for LLM-assisted development** (type information enables better code generation and reasoning)
- - IDE autocomplete and refactoring support
- - Self-documenting code (types visible in signatures)
- - Large codebases benefit from explicit interfaces
+ **Type parameter syntax (3.12+, PEP 695):** Use the bracket syntax for generics:
- **Where to use type hints (default: everywhere):**
- - All public APIs and module boundaries (required)
- - All function signatures: parameters and return types (required)
- - Class attributes, especially in `__init__` (required)
- - Complex data structures (required)
- - Internal functions in non-trivial modules (recommended)
- - Local variables only when type isn't obvious (optional)
+ ```python
+ # Modern (3.12+)
+ def first[T](items: list[T]) -> T: ...
+ type Vector[T] = list[T]
- **When type hints can be skipped:**
- - Throwaway scripts (< 50 lines, one-time use)
- - Local variables with obvious types from context
- - When type checker limitations force objectively worse code (rare, file a bug)
+ # Legacy (pre-3.12) — don't use in new code targeting 3.12+
+ from typing import TypeVar
+ T = TypeVar('T')
+ def first(items: list[T]) -> T: ...
+ ```
- **Staff insight:** Type hints aren't optional for production code. They provide explicit contracts that enable both humans and LLMs to reason about code. The "verbosity" argument is weak — good types make code more readable and maintainable. Use them everywhere except throwaway scripts.
+ **Deferred annotations (3.14+, PEP 649/749):** Annotations are now evaluated lazily by default. `from __future__ import annotations` (PEP 563) is superseded — don't use it in new code. It still works but has different semantics: PEP 563 stringifies annotations, while PEP 649 stores an evaluator function. For code targeting 3.10-3.13, PEP 563 remains useful for forward references.
- **Modern type hint features:**
- - `from __future__ import annotations` for forward references (use in 3.7-3.9)
+ **Other useful features:**
- `TypedDict` for structured dictionaries
- - `Protocol` for structural subtyping (duck typing with types)
+ - `Protocol` for structural subtyping (i.e., duck typing with types)
- `ParamSpec` and `Concatenate` for higher-order functions
- `typing.assert_never` for exhaustiveness checking in match statements
-
- ### Sphinx Documentation Is Mandatory
-
- Python documentation uses Sphinx with reStructuredText (or MyST for Markdown). Comprehensive documentation is not optional.
+ - `@override` decorator (3.12+) for explicit method overriding
+ </modern_type_syntax>
- **Documentation requirements:**
+ <type_checkers>
+ ### Type Checkers
- **Every module:**
- - Module-level docstring explaining purpose and main components
- - Examples of common usage patterns
- - Important considerations, limitations, or edge cases
+ **ty (Astral):** Preferred for most projects. Written in Rust; dramatically faster than alternatives. Currently in beta (v0.0.x) but effective at catching real bugs in typical codebases. Use ty unless you need Pydantic or Django ORM integration, which it doesn't yet support.
- **Every public class:**
- - Class docstring with clear purpose
- - Explanation of responsibilities and invariants
- - Usage examples for non-trivial classes
- - Attributes documented with `:ivar:` or in class docstring
+ **mypy:** The reference implementation with the broadest ecosystem support. Use mypy when you need its plugin API for dynamic frameworks (e.g., Pydantic, SQLAlchemy, Django ORM) or when your project already uses it. Configure with `strict = true` in `pyproject.toml`.
- **Every public function/method:**
- - One-sentence summary (first line)
- - Detailed description of purpose and behavior
- - Parameters documented with `:param:` and `:type:` (even with type hints - doc serves different purpose)
- - Return value documented with `:returns:` and `:rtype:`
- - Raised exceptions documented with `:raises:`
- - Usage examples for non-trivial functions
- - Important notes about edge cases, performance, or thread safety
+ **pyright/basedpyright:** The dominant IDE type checker (powers Pylance in VS Code). Richer type narrowing than mypy; checks unannotated code by default. Consider basedpyright for LSP integration in non-VS Code editors.
- **Every test:**
- - Docstring explaining what behavior is being tested
- - Why the test exists (what requirement it validates)
- - Special considerations (test data setup, known limitations)
+ | Context | Recommendation |
+ |---------|---------------|
+ | New project, no Pydantic/Django | ty |
+ | Pydantic or Django ORM | mypy (with framework plugin) |
+ | IDE/LSP experience | pyright or basedpyright |
+ | Existing mypy project | Keep mypy |
+ | Maximum strictness in CI | mypy `--strict` or basedpyright |
+ </type_checkers>
+ </type_system>
- **Sphinx docstring format:**
+ ## Documentation
- ```python
- def process_items(
- items: list[Item],
- filter_func: Callable[[Item], bool] | None = None,
- max_count: int = 100
- ) -> list[Item]:
- """Process a list of items with optional filtering.
+ <documentation_requirements>
+ ### Sphinx Docstrings
- This function processes items by applying an optional filter function
- and limiting results to a maximum count. Processing maintains the
- original order of items.
+ Python documentation uses Sphinx with reStructuredText. Comprehensive documentation is mandatory for all production code.
- :param items: The list of items to process. Must not be empty.
- :type items: list[Item]
- :param filter_func: Optional function to filter items. If None,
- all items are included.
- :type filter_func: Callable[[Item], bool] | None
- :param max_count: Maximum number of items to return. Must be
- positive.
- :type max_count: int
- :returns: Processed and filtered items, up to max_count.
- :rtype: list[Item]
- :raises ValueError: If items list is empty or max_count is not
- positive.
- :raises TypeError: If filter_func is not callable.
+ **Every module:** Module-level docstring explaining purpose and main components.
- Example usage::
+ **Every public class:** Class docstring with purpose, responsibilities, and invariants.
- items = [Item(1), Item(2), Item(3)]
- result = process_items(items, lambda x: x.value > 1, max_count=10)
+ **Every public function/method:**
+ - One-sentence summary (first line)
+ - Parameters with `:param:` and `:type:` (when `:type:` adds constraints beyond the signature)
+ - Return value with `:returns:` and `:rtype:`
+ - Exceptions with `:raises:`
+ - Usage examples for non-trivial functions
- .. note::
- This function does not modify the input list. A new list is
- returned.
+ **Every test:** Docstring explaining what behavior is being tested and why.
- .. warning::
- For very large lists (>10000 items), consider using
- :func:`process_items_streaming` instead for better memory
- efficiency.
- """
- # Implementation
- ```
+ See `references/docstring-example.py` for a complete example.
- **Sphinx formatting guidelines:**
+ <docstring_type_annotations>
+ ### When to Include `:type:` Annotations
- **ReStructuredText elements:**
- - Use proper reST formatting (no Markdown in docstrings)
- - Code examples in `::` blocks with proper indentation
- - Cross-references with `:func:`, `:class:`, `:meth:`, `:mod:`
- - Emphasis with `*italic*` and `**bold**`
- - Inline code with double backticks: ``code``
- - Lists with proper bullet/numbered formatting
+ Include `:type:` alongside type hints only when it adds information beyond the signature:
- **Semantic markup:**
- - Use `.. note::` for important information
- - Use `.. warning::` for critical gotchas or edge cases
- - Use `.. deprecated::` for deprecated functionality
- - Use `.. versionadded::` and `.. versionchanged::` for API evolution
+ | Situation | Include `:type:`? | Example |
+ |-----------|------------------|---------|
+ | Type hint says `int`, no constraints | No | Signature suffices |
+ | Parameter must be positive | Yes | `:type: int (must be positive)` |
+ | Accepts specific enum values | Yes | `:type: str ("json" or "xml")` |
+ | Complex generic with usage notes | Yes | Explain expected structure |
+ | Simple `str`, `bool`, `list[str]` | No | Signature suffices |
- **Why both type hints AND Sphinx `:type:` annotations:**
- - Type hints: Machine-readable, for static analysis and LLMs
- - Sphinx `:type:`: Human-readable, can include constraints and context
- - Example: Type hint is `int`, Sphinx says ":type: int (must be positive)"
+ Omit `:type:` when it would mechanically duplicate the signature. The goal is useful documentation, not ceremony.
+ </docstring_type_annotations>
- **Documentation philosophy:**
- - Document WHY, not just WHAT (explain purpose and design choices)
- - Include usage examples for non-obvious functionality
- - Explain limitations and edge cases
- - Assume readers are junior engineers or LLMs needing context
- - Good documentation describes **why something exists** and **how to use it correctly**, not just repeating the signature
+ <docstring_style_choice>
+ ### Docstring Style
- **Private members:**
- - Private functions/methods still need docstrings (prefix with underscore)
- - Explain intended use within the module
- - Document assumptions and invariants
+ For **new projects**, use Sphinx/reST style (`:param:`, `:type:`, `:returns:`, `:rtype:`, `:raises:`). For **existing projects**, follow the established convention — don't convert a Google-style codebase to Sphinx mid-project. pydoclint supports all three styles (`sphinx`, `google`, `numpy`); configure it to match your project.
+ </docstring_style_choice>
+ </documentation_requirements>
- **Staff insight:** Comprehensive Sphinx documentation is as important as type hints. Type hints tell you the types; documentation tells you why the function exists, how to use it correctly, and what can go wrong. Both are mandatory for production code.
+ ## Python Tooling
<python_tooling>
- ### Python Tooling Requirements
-
- All new Python projects must use modern tooling for dependency management, formatting, linting, and type checking.
-
- **Mandatory tools for all new projects:**
-
- **Hatch (project management):**
- - Modern Python project manager replacing setuptools
- - Manages environments, builds, and publishing
- - Standardized project structure (PEP 621 pyproject.toml)
- - Built-in environment isolation
- - Use for: All new projects (no exceptions)
+ ### Mandatory Tools
- **UV (package installation):**
- - Ultra-fast Python package installer (10-100x faster than pip)
- - Written in Rust, drop-in pip replacement
- - Lock file support for reproducible builds
- - Use with Hatch for environment management
- - Use for: All new projects (no exceptions)
+ **Ruff (linting and formatting):**
+ - Replaces Black, Flake8, isort, pydocstyle, and pyupgrade in a single Rust-based tool
+ - `ruff check` for linting, `ruff format` for Black-compatible formatting
+ - 10-100x faster than the tools it replaces
+ - Includes partial Bandit rules (`S` rule set) for common security checks
+ - Configure in `pyproject.toml` under `[tool.ruff]`
- **Black (code formatting):**
- - Uncompromising code formatter ("the uncompromising formatter")
- - Zero configuration, deterministic formatting
- - Ends formatting debates (consistency over personal preference)
- - Must be enabled in CI/CD pipeline
- - Configure in pyproject.toml, run on all code
+ **pydoclint (docstring linting):**
+ - Validates docstring sections (params, returns, raises) match function signatures
+ - Replaces darglint, which was archived December 2022 — **do NOT use darglint**
+ - Supports Sphinx, Google, and NumPy docstring styles
+ - Runs standalone or as a flake8 plugin (install with `pydoclint[flake8]`)
+ - Required because Ruff's DOC rules are still in preview and don't yet support Sphinx style or DOC101 (i.e., missing parameter detection)
+ - Configure in `pyproject.toml` under `[tool.pydoclint]`
- **Bandit (security linting):**
+ **Bandit (security linting — when needed):**
- Security vulnerability scanner for Python code
- - Catches common security issues (SQL injection, hardcoded passwords, etc.)
- - Must be enabled in CI/CD pipeline
- - Configure in pyproject.toml
-
- **Flake8 (style guide enforcement):**
- - PEP 8 style guide checker
- - Enforces code style consistency
- - Plugins available for additional checks
- - Must be enabled in CI/CD pipeline
- - Configure in .flake8 or pyproject.toml
-
- **MyPy (static type checking):**
- - Static type checker for Python
- - Enforces type hint correctness
- - Catches type errors before runtime
- - Must be enabled in CI/CD pipeline
- - Configure in pyproject.toml with strict settings
-
- **Example Project setup:**
-
- This example file shows how to set up a project using the above requirements. Make sure to check what the latest versions of Python and the various packages used are!
-
- ```toml
- # pyproject.toml example
- [build-system]
- requires = ["hatchling"]
- build-backend = "hatchling.build"
+ - Ruff's `S` rules cover most common security checks; use standalone Bandit only for security-critical projects needing full coverage (e.g., cryptographic vulnerability detection, severity classifications)
+ - Configure in `pyproject.toml`
- [project]
- name = "my-project"
- version = "0.1.0"
- description = "Project description"
- requires-python = ">=3.10"
- dependencies = [
- "dependency1>=1.0",
- ]
+ **Type checker:** See `<type_checkers>` — use ty, mypy, or pyright depending on your project's needs.
- [tool.hatch.envs.default]
- dependencies = [
- "pytest>=7.0",
- "black>=23.0",
- "flake8>=6.0",
- "mypy>=1.0",
- "bandit>=1.7",
- ]
+ <project_management>
+ ### Project Management
- [tool.black]
- line-length = 88
- target-version = ['py310']
+ **uv (primary project tool):**
+ - Ultra-fast Python package installer, project manager, and Python version manager
+ - Handles project initialization (`uv init`), dependency management (`uv add`/`uv remove`), lockfiles (`uv.lock`), building (`uv build`), publishing (`uv publish`), and script execution (`uv run`)
+ - Use for all new projects
- [tool.mypy]
- python_version = "3.10"
- warn_return_any = true
- warn_unused_configs = true
- disallow_untyped_defs = true
- disallow_incomplete_defs = true
- check_untyped_defs = true
- strict = true
+ **Hatch (matrix testing):**
+ - Use alongside uv specifically when you need declarative local multi-Python-version testing:
+ ```toml
+ [[tool.hatch.envs.hatch-test.matrix]]
+ python = ["3.12", "3.13", "3.14"]
+ ```
+ Then `hatch test --all` runs tests across all versions locally.
+ - If you only test against one Python version locally and rely on CI for matrix testing, uv alone suffices
+ - Hatch also provides VCS-driven versioning (`hatch-vcs`) and custom build hooks if needed
- [tool.bandit]
- exclude_dirs = ["tests", "test_*.py"]
- ```
+ | Workflow | Tool |
+ |----------|------|
+ | New project setup | `uv init --package` |
+ | Add dependencies | `uv add <pkg>` |
+ | Run tests | `uv run pytest` |
+ | Build and publish | `uv build && uv publish` |
+ | Local multi-version testing | Hatch (`hatch test --all`) |
+ | CI matrix testing | uv + GitHub Actions matrix |
+ </project_management>
### Dependency Version Specification
- **NEVER guess or assume dependency versions.** Verify current versions on PyPI before adding any dependency to `pyproject.toml`.
+ **NEVER guess or assume dependency versions.** Verify current versions on PyPI before adding any dependency.
| Constraint | When to Use |
|------------|-------------|
- | `>=MAJOR.MINOR` | Default—allows patch updates, guards against old bugs |
+ | `>=MAJOR.MINOR` | Default — allows patch updates, guards against old bugs |
| `>=MAJOR.MINOR,<NEXT_MAJOR` | When major version breaks are likely |
- | `==EXACT` | Avoid—use lock files for reproducibility instead |
-
- **Staff insight:** LLMs confidently hallucinate version numbers. A guessed `>=0.3` when the current version is `1.1` invites breaking changes; a guessed `>=2.0` for a package at `1.5` fails on install. The cost of a PyPI search is trivial compared to debugging phantom compatibility issues. This is especially critical for fast-moving ecosystems (LangChain, ML libraries) where major versions ship monthly.
-
- **CI/CD integration:**
- - All tools must run in CI/CD pipeline (GitHub Actions, GitLab CI, etc.)
- - Builds fail if any tool reports errors
- - No exceptions for "I'll fix it later"
+ | `==EXACT` | Avoid — use lock files for reproducibility instead |
- **Why these tools are mandatory:**
- - **Consistency:** Black eliminates formatting arguments
- - **Security:** Bandit catches vulnerabilities early
- - **Quality:** Flake8 and MyPy enforce code standards
- - **Speed:** UV and Hatch make development faster
- - **Modern:** These are current best practices (not legacy tools)
+ **Staff insight:** LLMs confidently hallucinate version numbers. The cost of a PyPI search is trivial compared to debugging phantom compatibility issues. This is especially critical for fast-moving ecosystems where major versions ship monthly.
- **Staff insight:** Don't waste time debating formatting or choosing between pip/setuptools/Poetry. Use Black for formatting (no configuration), Hatch+UV for project management (modern, fast), and enable all linters/type checkers (catch problems early). These tools are mandatory, not optional.
+ See `references/pyproject-example.toml` for a starter project configuration.
</python_tooling>
+ ## Testing
+
<testing_ecosystem>
- ### Testing Ecosystem: pytest vs unittest
+ **For general testing philosophy and TDD principles, see the test-driven-development skill.** This section covers Python-specific practices.
- **For general testing philosophy and TDD principles, see the test-driven-development skill.** This section covers Python-specific testing practices.
+ **Use pytest** for all new projects. It's the community standard: plain functions, simple `assert` with introspection, powerful fixtures, rich plugin ecosystem. Use `unittest` only in existing codebases that already use it.
- Python has two major testing frameworks with different philosophies.
+ **Core testing principle** (restated from test-driven-development skill): Mock at architectural boundaries (e.g., external systems, injected dependencies), not internal implementation details. The `unittest.mock` module is still useful with pytest.
+ </testing_ecosystem>
- **unittest (standard library):**
- - Java-style xUnit framework
- - Classes, setUp/tearDown methods
- - Verbose assertion methods (`self.assertEqual`)
- - Built-in, no dependencies
+ ## Async Patterns
- **pytest (third-party, dominant):**
- - Plain functions, not classes
- - Simple `assert` statements with introspection
- - Powerful fixture system
- - Rich plugin ecosystem
+ <async_patterns>
+ Use async/await for I/O-bound concurrency with many concurrent connections (e.g., web servers, websockets, batch HTTP requests). Don't use it for CPU-bound tasks, simple scripts, or when overhead isn't justified.
- **When to use pytest:**
- - Starting new projects (it's the community standard)
- - Want fixtures over setUp/tearDown
- - Value concise test code
- - Need plugins (coverage, parameterization, markers)
+ **Common mistakes:**
+ - Mixing sync and async code (e.g., blocking the event loop with synchronous `requests` or `psycopg2`)
+ - Using sync libraries in async contexts — use aiohttp, asyncpg, motor, redis.asyncio instead
+ - Premature async adoption when threads suffice
- **When unittest is acceptable:**
- - Existing unittest codebase (don't rewrite working tests)
- - Can't add dependencies (embedded environments)
- - Team already knows unittest
+ Bridge sync-to-async with `asyncio.to_thread()` (3.9+) when needed for CPU-bound work in async contexts.
- **Staff insight:** pytest won. It's more Pythonic (simple assertions, no classes), more powerful (fixtures), and has better tooling. Use pytest unless there's a specific reason not to. The `unittest.mock` module is still useful even with pytest.
+ **Staff insight:** Start with synchronous code. Add threads for I/O concurrency. Move to async only when you have many concurrent connections and measurable evidence it helps.
+ </async_patterns>
- **Core testing principle (from test-driven-development skill):** Mock at architectural boundaries (external systems, injected dependencies), not internal implementation details.
- </testing_ecosystem>
+ ## Modern Python (3.12+)
- <async_patterns>
- ### Async/Await: Not a Silver Bullet
+ <modern_python>
+ <python_312_features>
+ ### Python 3.12
- Python 3.5+ has async/await for asynchronous I/O. It's powerful but often misunderstood.
+ - **PEP 695**: Type parameter syntax (`def func[T](x: T) -> T`) — see `<modern_type_syntax>`
+ - **`@override` decorator**: Explicit intent for method overriding, caught by type checkers
+ - **F-string improvements**: Multi-line expressions, nesting, reused quote types all allowed
+ - **`distutils` removed** from stdlib; use `uv build`
+ </python_312_features>
- **When async/await wins:**
- - I/O-bound tasks (network requests, database queries)
- - Many concurrent connections (web servers, websockets)
- - Can amortize event loop overhead (not single requests)
+ <python_313_features>
+ ### Python 3.13
- **When async/await doesn't help:**
- - CPU-bound tasks (GIL still applies)
- - Blocking libraries (most DB drivers are synchronous)
- - Simple scripts (overhead not justified)
+ - **Free-threaded build (experimental)**: GIL-optional via `--disable-gil` build flag; ~40% single-threaded overhead in this version
+ - **Improved REPL**: Color output, multi-line editing, better history
+ - **`locals()` semantics change**: Now returns a copy, not a proxy to the frame
+ </python_313_features>
- **Common mistakes:**
- - Mixing sync and async code (blocking the event loop)
- - Not using `async` libraries (sync `requests` blocks async code)
- - Premature optimization (threads often suffice)
+ <python_314_features>
+ ### Python 3.14 (Current Stable)
- **Staff insight:** Async isn't "free concurrency." You need async libraries (aiohttp, asyncpg, not requests/psycopg2). The event loop can't help if you're CPU-bound. Start with threads for I/O concurrency; move to async only if you have measurable evidence it helps.
- </async_patterns>
+ - **PEP 649/749**: Deferred annotation evaluation is now the default — see `<modern_type_syntax>`
+ - **PEP 750**: Template strings (`t"Hello {name}"`) — evaluate to `Template` objects instead of `str`, enabling safe SQL interpolation, HTML templating, and structured logging
+ - **PEP 779**: Free-threaded build is now **officially supported** (Phase II), no longer experimental; single-threaded overhead reduced to ~5-10%
+ - **PEP 758**: `except` clauses no longer require parentheses for multiple exceptions: `except TimeoutError, ConnectionError:`
+ - **PEP 734**: `concurrent.interpreters` — multiple interpreters per process with `InterpreterPoolExecutor`
+ - **PEP 768**: Zero-overhead external debugger interface; `pdb` can attach to running processes
+ </python_314_features>
- ### Python 2 vs 3: It's Over
+ <gil_and_concurrency>
+ ### The GIL and Concurrency
- Python 2 reached end-of-life in 2020. Don't write new Python 2 code.
+ Python's Global Interpreter Lock historically prevented true parallelism for CPU-bound threads. This is changing.
- **If maintaining Python 2 code:**
- - Six library for compatibility
- - 2to3 tool for automated migration
- - `__future__` imports for Python 3 behavior
+ **Current state (3.14):**
+ - The standard build still has the GIL (default behavior unchanged)
+ - The free-threaded build (PEP 779) removes the GIL and is officially supported, with ~5-10% single-threaded overhead
+ - Free-threading enables true parallel threads for CPU-bound work without `multiprocessing`
- **Python 3 benefits:**
- - Unicode strings by default (str, not bytes)
- - Better exception handling (chained exceptions)
- - Async/await support
- - Type hints
- - f-strings, pathlib, dataclasses
+ **Practical guidance today:**
+ - For I/O-bound concurrency: `asyncio` or threads (both work with or without GIL)
+ - For CPU-bound parallelism: `multiprocessing` remains the safe default; free-threaded builds are viable for early adopters
+ - `asyncio.to_thread()` (3.9+) bridges sync code into async contexts
- **Staff insight:** If you're stuck on Python 2, plan migration. If you're writing new code, use Python 3.10+ for modern features (match statements, union types with `|`).
+ **Staff insight:** Free-threading is the future but ecosystem support (e.g., C extensions, third-party libraries) is still maturing. For most projects, `multiprocessing` for CPU-bound work and threads/async for I/O-bound work remain the pragmatic choices.
+ </gil_and_concurrency>
+ </modern_python>
+ ## When Python Works and Struggles
+
+ <python_fitness>
+ **Python excels at:** Rapid prototyping, scripting and automation, data analysis (e.g., NumPy, pandas), web services (e.g., FastAPI, Django), and education.
+
+ **Python struggles with:** Performance-critical tight loops (use NumPy or drop to C/Rust), real-time systems, mobile development, systems programming, and CPU-bound parallelism (though free-threading is changing this).
+
+ **Staff insight:** Use Python where its strengths (e.g., development speed, ecosystem, readability) outweigh its weaknesses. Don't force it into low-level, high-performance, or mobile domains.
+ </python_fitness>
+
+ ## Common Pitfalls
+
+ <common_pitfalls>
<common_mistakes>
- ### Common Mistakes from Other Language Backgrounds
+ ### By Background
<from_java>
**From Java:**
- - Java-style getters/setters (use properties or public attributes)
- - Inheritance hierarchies (use composition, duck typing)
- - Checked exceptions (Python has no checked exceptions)
- - Verbose code (Python values conciseness)
+ - Class hierarchies where composition or duck typing suffice
+ - Getters/setters instead of properties or public attributes
+ - Checked exception thinking (Python has no checked exceptions)
+ - Over-verbose code where Python values conciseness
</from_java>
<from_c>
**From C/C++:**
- Manual memory management thinking (trust the garbage collector)
- - Pointer-like patterns (use references directly)
- - Low-level optimization (profile first, most code isn't bottleneck)
+ - Low-level optimization before profiling (most code isn't the bottleneck)
</from_c>
<from_javascript>
**From JavaScript:**
- - `var`/`let`/`const` thinking (Python has simpler scoping)
- - Callback hell (use async/await or just sequential code)
- - Prototypal inheritance (Python uses class-based)
+ - Callback patterns instead of async/await or sequential code
+ - Prototypal inheritance thinking (Python uses class-based)
</from_javascript>
-
- **Staff insight:** Each language has idioms. Don't write Java in Python. Read "Fluent Python" or "Effective Python" to internalize Pythonic thinking.
</common_mistakes>
- ### Dataclasses and attrs: Boilerplate Reduction
-
- Python 3.7+ has dataclasses for reducing class boilerplate. The `attrs` library is a more powerful alternative.
-
- **When to use dataclasses:**
- - Simple data containers (replacing namedtuples)
- - Want `__init__`, `__repr__`, `__eq__` generated
- - Type hints for documentation
- - Frozen classes for immutability
-
- **When to use attrs:**
- - Need validators, converters, or defaults with factories
- - Python < 3.7 (attrs works on 2.7+)
- - Want more features (slots, metadata)
-
- **When to skip both:**
- - Dynamic attributes (use plain class or dict)
- - Very few classes (boilerplate isn't a problem)
- - Duck typing over structure (dataclasses imply structure)
-
- **Staff insight:** Dataclasses aren't a replacement for all classes — they're for data-focused classes. Use them for configuration, API responses, value objects. Don't shoehorn behavior-heavy classes into dataclasses.
-
- ### The GIL and Concurrency
-
- Python's Global Interpreter Lock (GIL) prevents true parallelism for CPU-bound tasks within a single process.
-
- **What the GIL means:**
- - Only one thread executes Python bytecode at a time
- - Threads help with I/O-bound tasks (release GIL during I/O)
- - Threads don't help with CPU-bound tasks (GIL is bottleneck)
-
- **Working around the GIL:**
- - multiprocessing for CPU-bound parallelism (separate processes)
- - NumPy/Cython release GIL for numerical computation
- - async/await for I/O concurrency (not parallelism)
-
- **Staff insight:** The GIL isn't Python's flaw — it's a design choice that simplifies the interpreter and C extension integration. For CPU-bound work, use multiprocessing or drop to native code. For I/O-bound work, threads or async suffice.
-
- ### Modern Python Features (3.10+)
-
- **Pattern matching (3.10):**
- - Match statements for structural pattern matching
- - Good for parsing, dispatching on types/structures
- - Don't overuse (if/elif often clearer for simple cases)
-
- **Union types with `|` (3.10):**
- - `int | None` instead of `Optional[int]`
- - Cleaner type hint syntax
-
- **Structural pattern matching trade-offs:**
- - More expressive than if/elif chains for complex cases
- - Overkill for simple type checking
- - Pattern matching is not switch/case (more powerful)
-
- **Staff insight:** Modern features are nice but not necessary. Use them where they improve clarity. Don't rewrite code just to use new syntax.
- </staff_level_insights>
-
- <common_pitfalls>
- ## Common Pitfalls and Anti-Patterns
-
- ### Late Binding in Closures
-
- Closures capture variables by reference, not value. This trips up loop-generated functions.
-
- **The problem:**
- ```python
- funcs = [lambda: i for i in range(3)]
- [f() for f in funcs] # [2, 2, 2] - all see final 'i'
- ```
-
- **The fix (default argument):**
- ```python
- funcs = [lambda i=i: i for i in range(3)]
- [f() for f in funcs] # [0, 1, 2]
- ```
-
- **Staff insight:** This is Python's scoping behavior. Closures bind variables, not values. Use default arguments to capture values, or use partial application from functools.
-
- ### Comparing to True/False/None
-
- Use truthiness checks, not explicit comparisons.
-
- **Unpythonic:**
- ```python
- if x == True:
- if len(items) == 0:
- if x == None:
- ```
-
- **Pythonic:**
- ```python
- if x:
- if not items:
- if x is None:
- ```
-
- **Exception:** Use `is` for singletons (None, True, False). Use `==` for value comparison.
-
- **Staff insight:** Python's truthiness is powerful. Empty containers, zero, None, False are all falsy. Use it. But be explicit when checking for None specifically (use `is None`, not just `not x`).
-
- ### Lambda Assignment
-
- Don't assign lambdas to variables — use `def` instead.
-
- **Unpythonic:**
- ```python
- add = lambda x, y: x + y
- ```
-
- **Pythonic:**
- ```python
- def add(x, y):
- return x + y
- ```
-
- **Why:** Lambdas are for anonymous functions passed as arguments. Named functions get better tracebacks and documentation.
-
- **Staff insight:** Linters flag this (PEP 8 E731). Use lambdas inline, not assigned. If it needs a name, use def.
-
- ### Global Statement Abuse
-
- Avoid `global` except in rare cases. It makes code hard to reason about.
-
- **When global is acceptable:**
- - Module-level configuration (though classes or functions are better)
- - Caching/memoization (use `functools.lru_cache` instead)
- - Truly global state (rare)
-
- **Better alternatives:**
- - Pass parameters explicitly
- - Use classes to encapsulate state
- - Return values instead of modifying globals
+ <python_specific_pitfalls>
+ ### Python-Specific
- **Staff insight:** Global state is a code smell in any language. Python doesn't forbid it, but avoid it. Explicit is better than implicit.
+ - **Late binding in closures**: Closures capture variables by reference. Use default arguments (`lambda i=i: i`) or `functools.partial` to capture values in loop-generated functions
+ - **Truthiness confusion**: Empty containers, zero, `None`, and `False` are all falsy. Use `is None` when checking for `None` specifically, not `not x`
+ - **`global` abuse**: Pass parameters explicitly or use classes. `functools.lru_cache` replaces most caching-via-global patterns
+ - **Import cycles**: Use `TYPE_CHECKING` guard for type-only imports; restructure modules to break real circular dependencies
+ - **`pathlib` neglect**: Prefer `pathlib.Path` over `os.path` for path manipulation — it's more readable and less error-prone
+ - **Lambda assignment**: Don't assign lambdas to variables (PEP 8 E731) — use `def` for named functions
+ </python_specific_pitfalls>
</common_pitfalls>
- <safety_constraints>
- ## Safety Constraints
+ ## Dataclasses and attrs
- - **NEVER** use mutable default arguments (lists, dicts, sets) without the `None` sentinel pattern
- - **NEVER** assign lambdas to variables—use `def` for named functions
- - **NEVER** use `global` for shared state—use classes or explicit parameter passing
- - **NEVER** catch bare `Exception` and swallow errors silently
- - **NEVER** use `eval()` or `exec()` on untrusted input
- - **NEVER** sacrifice readability for cleverness—a 4-line loop beats a cryptic 1-line comprehension
- - **ALWAYS** use context managers (`with`) for file handles, locks, and database connections
- - **ALWAYS** use parameterized queries—never string concatenation for SQL
- - **ALWAYS** validate and sanitize untrusted input at system boundaries
- - **ALWAYS** prefer explicit, readable code over clever tricks that require mental parsing
- </safety_constraints>
+ <data_classes>
+ Use dataclasses (3.7+) for simple data containers. Use `slots=True` (3.10+) for memory efficiency and faster attribute access. Use `frozen=True` for immutability. Use `attrs` when you need validators, converters, or `evolve()` — its `define`/`field` API is more powerful than dataclasses for complex cases.
- <resources>
+ Dataclasses aren't a replacement for all classes — they're for data-focused classes. Use them for configuration, API responses, and value objects. Don't shoehorn behavior-heavy classes into dataclasses.
+ </data_classes>
+
## Resources
+ <resources>
**Official Documentation:**
- Python Documentation: https://docs.python.org/3/
- PEP Index: https://peps.python.org/
- PEP 8 Style Guide: https://peps.python.org/pep-0008/
- - PEP 484 Type Hints: https://peps.python.org/pep-0484/
**Tooling:**
+ - Ruff Documentation: https://docs.astral.sh/ruff/
+ - uv Documentation: https://docs.astral.sh/uv/
+ - ty Documentation: https://docs.astral.sh/ty/
+ - pydoclint Documentation: https://jsh9.github.io/pydoclint/
- MyPy Documentation: https://mypy.readthedocs.io/
- pytest Documentation: https://docs.pytest.org/
- - Black Documentation: https://black.readthedocs.io/
- Hatch Documentation: https://hatch.pypa.io/
**Style Guides:**
- Google Python Style Guide: https://google.github.io/styleguide/pyguide.html
</resources>
<summary>
## Summary
Python programming emphasizes:
- **Readability over cleverness** — Code is read more than written; don't show off
- - **Type hints everywhere** — Essential for code quality and LLM-assisted development
- - **Comprehensive Sphinx documentation** — Mandatory for all production code
- - **Modern tooling** — Hatch+UV for project management, Black/Bandit/Flake8/MyPy for quality
+ - **Type hints everywhere** — Essential for code quality and LLM-assisted development; use PEP 695 syntax on 3.12+
+ - **Comprehensive documentation** — Sphinx docstrings with meaningful content, not mechanical `:type:` duplication
+ - **Modern tooling** — Ruff for linting/formatting, pydoclint for docstring validation, uv for project management, ty or mypy for type checking
- **EAFP over LBYL** — Try and catch exceptions rather than checking first
- - **Duck typing** — Accept behavior, not types
+ - **Duck typing with Protocols** — Accept behavior, not types
- **Simple Pythonic idioms** — Comprehensions for simple cases, explicit loops for complex ones
- - **Pragmatism over purity** — Python isn't purely functional or OO
- Apply Python where it excels (scripting, prototyping, data processing, web APIs) and use other languages where it struggles (performance-critical, mobile, systems programming). **All new projects must use Hatch+UV, with Black, Bandit, Flake8, and MyPy enabled in CI/CD.** Type hints and Sphinx documentation are mandatory for all production code (exceptions: throwaway scripts). Choose pytest over unittest, understand async/await limitations, and avoid anti-patterns from other language backgrounds. Success in Python comes from embracing its philosophy: readable, explicit, well-documented, well-tooled code.
+ **All new projects must use uv for project management, with Ruff, pydoclint, and a type checker (ty or mypy) enabled in CI/CD.** Do NOT use darglint (archived 2022). Type hints and documentation are mandatory for all production code (exception: throwaway scripts). Follow existing project conventions rather than fighting established codebases.
</summary>