python-coding-standards
Python Coding Standards
Produce Python a colleague can follow: named pieces with clear intent, the smallest project shape that fits, direct control flow, ordinary names, and an explanation wherever code cannot show the reason. Review or diagnosis returns findings without implementing them. A change request includes its relevant local checks and nothing more: no repository-wide migration, cleanup, restructuring, or publication, and a pre-existing problem met on the way is a follow-up in the summary, not a change.
Start from the repository
Read the project instructions, Python support range, dependency constraints, nearby implementation and tests, and configured checks before choosing syntax, layout, or tools. Existing public imports, accepted inputs, serialized values, and persisted data stay compatible unless the request includes a migration. The current request takes precedence, then explicit repository constraints, then this skill's defaults, which apply to new and changed code; mixed existing style is not an exemption. Preserve configured formatters, linters, type checkers, and test runners unless their change is in scope, and report a conflict with older Python or incompatible tooling instead of breaking support.
Name the responsibilities
Give each function a caller-visible responsibility and make data flow and side effects clear. When a new module or multi-phase task has unclear boundaries, a skeleton of annotated signatures and short contract docstrings can expose them before implementation. Use it when it helps; an existing cohesive function or a small change does not need a separate skeleton pass. Revise boundaries as the implementation supplies evidence. Names that only describe position (step_two, process_data, helper) prompt a review of the responsibility. Resolve a consequential contract ambiguity from the request or repository; do not turn an unverified assumption into a promised docstring contract.
A block is its own function when a reader would otherwise have to work out what it does and it hands back one named value, decision, or effect through a small signature. Call count decides nothing: a responsibility called once still earns a function, and a "one-time operation" that needs no helper is a statement or two, not a named step of the algorithm. Keep a block inline when extracting it would need more than about four inputs, return several unrelated locals, or leave either function unreadable without the other. Flatten before splitting, with early returns and a mapping or match in place of an if/elif chain over constants. Keep parsing, computation, and output as phases joined by a model; I/O and mutation of shared state live in the orchestrating function, the computation beneath stays pure, and a function that mutates says so in its name or docstring. A comment that says what a block does is a prompt to extract or rename; a comment that says why stays.
A finished function has a name and contract that express one coherent responsibility, a caller whose flow and side effects are clear from its callees, and a boundary that improves understanding. A conjunction such as "and" can describe related parts of one responsibility; inspect the meaning rather than banning a word. A complexity or statement-count lint finding prompts a review of decomposition under the repository's rules, never a split by line ranges into numbered helpers or a bare noqa. Read functions and modules for an optional skeleton, the intent tests, the lint gates a new project adopts, and the measured function sizes behind them.
Choose the project shape
Pick the row from the project kind before creating files, and choose per feature: a CRUD feature stays flat beside a feature with ports.