programming-python
SkillPythonic architecture, type-driven design, and library choices that scale beyond scripts. Auto-activates in Python projects.
Instructions
Overview
Write Python with clear data contracts, deliberate effects, and predictable failure behavior. Research baseline: Python 3.14 is stable; 3.15 is prerelease, checked 2026-09-05. Read requires-python, environment pins, dependency lockfiles, type-checker settings, and CI first. Use features supported by every required interpreter; don't raise the minimum version or replace the project's tooling during unrelated work. Recheck official releases when updating this guidance.
Mental model
Annotations describe a contract; validation enforces it. Keep boundary parsing explicit, represent trusted data with small types, and make ownership of mutable state and resources visible. A function or module is enough until a class or abstraction solves a concrete lifecycle or substitution problem.
Recent features and migration traps
| Version | Useful change and boundary |
|---|---|
| 3.11 | TaskGroup, exception groups/except*, and asyncio.timeout support structured concurrent failure handling. |
| 3.12 | type Alias = ... and type-parameter syntax (def first[T](...)) simplify typed APIs; older interpreters cannot parse them. |
| 3.13 | TypeIs supports narrowing both branches; use it only when its condition exactly identifies the claimed type. |
| 3.14 | Annotations are deferred by default; annotationlib supports introspection. T-strings produce Template objects, and free-threaded builds are officially supported. |
Don't assume annotation values are eagerly evaluated or all strings. Use typing.get_type_hints when evaluated types are needed, or appropriate annotationlib formats for introspection; evaluating annotations can execute code. T-strings preserve literal and interpolated parts for a processor: they do not automatically escape HTML or parameterize SQL, and they are not interchangeable with str.
Types and data
- Annotate public boundaries and non-obvious internal contracts; use
list[T],dict[K, V], andT | Noneon supported versions. Useobjectfor unknown data that must be narrowed; isolate necessaryAnyat untyped integration points. TypedDictdescribes dictionary shape without runtime validation.Protocoldescribes structural behavior; a runtime-checkable protocol does not verify method signatures or semantic correctness.- Use dataclasses for records; choose
frozen=Truewhen reassignment is unwanted andslots=Truewhen its restrictions fit. Frozen fields do not freeze nested lists or dictionaries. Usedefault_factoryfor mutable defaults. - Validate untrusted values once at entry and pass trusted values inward. Choose strict validation or explicit coercion deliberately; don't let a serializer or modeling library quietly change the input contract.
- Use an explicit
is Nonecheck when zero,False, or an empty collection is valid. Truthiness-based defaults can erase meaningful input.
Errors, resources, and module design
- Catch specific failures close to the operation that can recover. A top-level handler may catch broadly to report failure, but should not pretend success. Add a domain exception when callers need that distinction; preserve causes with
raise ... from .... - Don't replace missing required configuration, failed I/O, or invalid input with defaults. Suppress only a named, expected failure whose absence is part of the contract. Keep sensitive input out of exception messages and logs.
- Use
with/async withfor resources andExitStackfor dynamic groups. Make cleanup observable where failure matters; finalizers and garbage collection do not guarantee timely cleanup. - Keep imports free of heavyweight work and mutable global configuration. Public submodules are valid APIs; re-exporting everything from
__init__.pyis unnecessary and can introduce cycles. - Use
pyproject.tomlfor modern package metadata. Asrc/layout helps test installed-package behavior; flat layouts and requirements files still have valid uses. Preserve the established package/environment tools unless changing them is part of the task.
Concurrency
- Use async I/O for concurrent awaitable operations and threads for blocking I/O. In a GIL-enabled interpreter, pure-Python CPU work generally needs processes, isolated interpreters, or native code that releases the GIL for parallelism.
- Free-threaded builds can execute Python threads in parallel, but extensions may re-enable the GIL. Verify runtime and dependency support; protect compound shared mutations with locks rather than relying on container internals.
TaskGroupcancels siblings after an ordinary task failure and waits for cleanup. PropagateCancelledErrorafter necessary cleanup; swallowing it can break task groups and timeouts.gatherhas different failure semantics and remains useful when those semantics are intended.- Keep blocking work off the event loop with appropriate executors or
to_thread. Bound queued work; cancelling an awaiting coroutine does not necessarily stop a running thread. - Python 3.14 no longer defaults to
forkon any platform. Make process-pool entry points importable, guard startup withif __name__ == "__main__":, and explicitly choose a multiprocessing context only when required.InterpreterPoolExecutorprovides isolated interpreters, not shared mutable globals.
Example
A missing optional value may use a default; an invalid supplied value must fail:
def parse_workers(raw: str | None) -> int:
if raw is None:
return 4
try:
workers = int(raw)
except ValueError as exc:
raise ValueError("worker count must be an integer") from exc
if workers < 1:
raise ValueError("worker count must be positive")
return workersChecklist
- Syntax and libraries match the supported Python versions and interpreter build.
- Types are validated at boundaries; mutable defaults and truthiness don't change meaning.
- Errors, cleanup, cancellation, and worker shutdown preserve the contract.
- When authorized, use the project's formatter, linter, type checker, and focused tests. Cover invalid input and concurrent cleanup; report skipped checks without installing missing tools implicitly.