Python
Readability counts. Explicit is better than implicit. If your code needs a comment to explain its control flow,
restructure it.
Python 3.14+ is the baseline. Use modern syntax unconditionally — no backward compatibility with older Python versions
unless the project explicitly requires it.
References
- [
${CLAUDE_SKILL_DIR}/references/typing.md] — Type annotation patterns, generics, overloads, TypeVar, variance:
full annotation examples, generic class patterns, Protocol implementation, TypeVar usage
- [
${CLAUDE_SKILL_DIR}/references/packaging.md] — Project layout, pyproject.toml, uv, dependency management:
pyproject.toml templates, uv workflows, src layout, dependency groups, build backends
- [
${CLAUDE_SKILL_DIR}/references/modules.md] — Module system, imports, namespace packages, __init__.py: import
resolution order, circular import fixes, lazy imports, namespace packages
- [
${CLAUDE_SKILL_DIR}/references/concurrency.md] — asyncio, TaskGroup, cancellation, timeouts, threading interop:
TaskGroup error handling, timeout scopes, cancellation semantics, to_thread, eager task factory
Naming
Variables, functions, methods — snake_case: user_name, fetch_data
Classes, type aliases — PascalCase: UserService, HttpClient
Constants — UPPER_SNAKE_CASE: MAX_RETRIES, API_BASE_URL
Modules, packages — snake_case, short: user_store, auth
Private attributes/methods — _ prefix: _internal_cache, _validate()
Name-mangled attributes — __ prefix: __secret (rarely needed)
Type variables — PascalCase, short: T, KT, VT, ResponseT
Protocols — PascalCase, -able/-ible suffix: Renderable, Serializable
Descriptive names. user_count not n. Short names (i, x) only in tiny scopes (comprehensions, simple
lambdas).
No redundant context. car.make not car.car_make.
Boolean names: is_/has_/can_/should_ prefix: is_valid, has_access.
Dunder methods are reserved for the data model. Never invent custom dunder names.
Avoid single-character names outside loop indices, comprehension variables, and well-established conventions (f
for file, e for exception, k/v for key/value).
Type Annotations
Python 3.14+ uses modern annotation syntax natively. No from __future__ import annotations needed — all annotations
are evaluated lazily by default.
Core Rules
- Annotate all public API boundaries — function signatures, class attributes, module-level variables. Internal code
often needs fewer annotations; types flow from context.
- Use built-in generics:
list[str], dict[str, int], tuple[int, ...], set[float]. Never import List,
Dict, Tuple, Set from typing.
- Union with
|: str | None, int | float. Never Optional[X] or Union[X, Y].
type statement for aliases: type Vector = list[float]. Not TypeAlias annotation.
None return: annotate -> None on functions that return nothing. Omit return type only on __init__.
- Avoid
Any — it disables type checking. Use object when you mean "any type but still type-safe." Use Any only
at true interop boundaries with untyped code.
Generics
type parameter syntax (3.12+): class Stack[T]: and def first[T](items: list[T]) -> T: instead of TypeVar
declarations.
- Constrained type parameters:
def process[T: (str, bytes)](data: T) -> T: for a finite set of allowed types.
- Bounded type parameters:
def sort[T: Comparable](items: list[T]) -> list[T]: for upper-bound constraints.
- Variance is inferred from usage in 3.12+ generics. No manual
covariant/contravariant flags needed.
Protocols (Structural Typing)
- Prefer protocols over ABCs when you don't control the implementing types or when structural compatibility is
sufficient.
@runtime_checkable only when you need isinstance() checks — it adds overhead and only validates method
presence, not signatures.
- Keep protocols small — one to three methods. A protocol with many methods is a sign you need an ABC or a concrete
base class.
from typing import Protocol, runtime_checkable
@runtime_checkable
class Renderable(Protocol):
def render(self) -> str: ...
Callable Types
collections.abc.Callable for callable annotations: Callable[[int, str], bool].
ParamSpec for decorators that preserve signatures:
def decorator[**P, R](fn: Callable[P, R]) -> Callable[P, R]:.
- Use
Protocol for complex callable signatures with keyword arguments or overloads.
TypeGuard and TypeIs
TypeIs (3.13+) for narrowing that refines the input type:
def is_str_list(val: list[object]) -> TypeIs[list[str]]:.
TypeGuard for narrowing where the output type is unrelated to input:
def is_valid_config(data: object) -> TypeGuard[Config]:.
See ${CLAUDE_SKILL_DIR}/references/typing.md for full annotation patterns, generics, overloads, and variance.
Data Classes and Structured Data
dataclasses
- Use
@dataclass for data containers — classes that primarily hold data with minimal behavior.
frozen=True for immutable data: @dataclass(frozen=True). Default to frozen unless mutation is required.
slots=True for memory efficiency and attribute safety: @dataclass(slots=True, frozen=True).
kw_only=True when constructors have more than 3 fields — prevents positional argument ordering bugs.
field(default_factory=list) for mutable defaults. Never use mutable default arguments.
- Post-init processing:
__post_init__ for derived fields and validation.
@dataclass(frozen=True, slots=True, kw_only=True)
class User:
name: str
email: str
roles: list[str] = field(default_factory=list)
When NOT to Use dataclasses
- Simple value containers with 1-2 fields: use
NamedTuple or plain tuples.
- Config/settings with validation: use Pydantic or attrs with validators.
- Persistence/ORM models: use the ORM's model base class.
NamedTuple
- Use
class syntax over functional form: class Point(NamedTuple): x: float; y: float.
- NamedTuples are immutable and iterable — useful as dict keys and in destructuring.
Enums
- Use
enum.Enum for categorical constants. Never use bare strings or ints as pseudo-enums.
enum.StrEnum when the enum must interoperate with string APIs (JSON, config keys).
enum.IntEnum only when integer interop is mandatory (legacy protocols). Prefer Enum otherwise.
@enum.unique to prevent duplicate values.
- Access by value:
Color(1). Access by name: Color["RED"]. Iteration: for c in Color:.
- Never subclass enums with members. Enums with members are final.
from enum import StrEnum, unique
@unique
class Status(StrEnum):
ACTIVE = "active"
INACTIVE = "inactive"
SUSPENDED = "suspended"
Pattern Matching
match/case (3.10+) is the preferred dispatch mechanism for structural patterns.
- Use match for structural dispatch — matching on type, shape, or destructured values. Don't use match as a
substitute for simple
if/elif chains on a single value.
- Always include a wildcard
case _: arm unless the match is provably exhaustive.
- Guard clauses with
if: case Point(x, y) if x > 0:.
- Use
| for alternatives: case "quit" | "exit" | "q":.
- Capture with walrus:
case {"error": str() as msg}: captures while matching type.
- Class patterns require
__match_args__ or keyword patterns: case Point(x=0, y=y):.
match command:
case {"action": "move", "direction": str() as direction}:
move(direction)
case {"action": "attack", "target": str() as target}:
attack(target)
case _:
raise ValueError(f"Unknown command: {command}")
Functions
- Early return. Guard clauses first, happy path flat. Reduce nesting.
- One function, one job. If the name contains "and", split it.
- Type-annotate all parameters and return types on public functions.
- Default arguments: immutable values only. Use
None + conditional for mutable defaults:
def f(items: list[int] | None = None): then items = items or [] in body. Never def f(items: list[int] = []):.
* to force keyword-only arguments after positional params: def connect(host: str, *, port: int = 443):.
/ to force positional-only for parameters that callers shouldn't name: def sqrt(x: float, /) -> float:.
- Prefer returning values over mutating arguments. Functions should be referentially transparent when possible.
None means absent, not error. Return T | None for optional results. Raise exceptions for errors.
Decorators
- Preserve signatures with
functools.wraps:def retry[**P, R](fn: Callable[P, R]) -> Callable[P, R]:
@functools.wraps(fn)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
...
return wrapper
- Decorator order matters. Decorators apply bottom-up.
@staticmethod and @classmethod must be outermost (topmost
in source).
- Parametric decorators return a decorator:
@retry(attempts=3) means retry returns the actual decorator
function.
- Don't over-abstract with decorators. If the decorator hides important control flow (error handling, transaction
management), make it explicit instead.
Context Managers
Generators and Iterators
- Generators for lazy sequences. Use
yield to produce values on demand instead of building full lists in memory.
- Generator expressions over list comprehensions when the result is iterated only once:
sum(x * x for x in range(1000)).
yield from to delegate to sub-generators — preserves .send(), .throw(), .close() protocol.
itertools for composition: chain, islice, groupby, batched (3.12+), pairwise (3.10+).
- Annotate generators:
def gen() -> Iterator[int]: for simple generators,
Generator[YieldType, SendType, ReturnType] when using .send().
- Never exhaust a generator twice. Generators are single-pass. If you need multiple passes, materialize to a list or
use
itertools.tee.
Comprehensions
- List/dict/set comprehensions for simple transforms:
[x.name for x in users if x.active].
- One level of nesting maximum. Two nested
for clauses are the absolute limit. Beyond that, extract to a function.
- Don't use comprehensions for side effects.
[print(x) for x in items] is wrong — use a for loop.
- Walrus operator in comprehensions for compute-once-filter-and-use:
[y for x in data if (y := transform(x)) is not None].
- Dict comprehensions for key transformations:
{k.lower(): v for k, v in headers.items()}.
Exception Handling
Strings
- f-strings for interpolation. Never
% formatting or .format() in new code.
- f-string expressions must be simple. No function calls with multiple arguments, no nested f-strings, no complex
expressions. Extract to a variable first.
str.removeprefix() / str.removesuffix() (3.9+) over slicing.
- Triple-quoted strings for multiline. Use
textwrap.dedent when indentation matters.
"".join(parts) for building strings in loops — never += in a loop.
- Raw strings
r"..." for regex patterns and Windows paths.
Pathlib
pathlib.Path for all filesystem operations. Never os.path in new code.
/ operator for path joining: base / "subdir" / "file.txt".
- Common operations:
path.exists(), path.is_file(), path.is_dir(), path.read_text(), path.write_text(),
path.mkdir(parents=True, exist_ok=True), path.iterdir(), path.glob("*.py"), path.rglob("**/*.py").
path.resolve() for absolute paths. path.relative_to(base) for relative paths.
- Accept
str | Path in public APIs, convert to Path internally.
Imports
- Absolute imports by default:
from mypackage.utils import helper.
- Relative imports only within packages for tightly coupled modules:
from .models import User.
- Import grouping (separated by blank lines):
- Standard library (
import os, from pathlib import Path)
- Third-party (
import httpx, from pydantic import BaseModel)
- Local (
from myapp.models import User)
- Import specific names:
from collections import defaultdict not import collections (unless you use many names
from the module).
- Never
from module import * — pollutes namespace, breaks type checkers, hides dependencies.
if TYPE_CHECKING: block for imports used only in annotations — avoids circular imports and runtime overhead. In
3.14+ with lazy annotations, this is less necessary but still useful for avoiding circular import side effects.
- Lazy imports in function bodies when a top-level import would create a circular dependency or when the import is
expensive and rarely needed.
Classes
Slots
- Always use
__slots__ on classes that will have many instances — prevents __dict__ creation, saves memory,
catches typos in attribute names.
@dataclass(slots=True) adds slots automatically.
- Slots and inheritance: every class in the hierarchy must declare
__slots__. Missing slots on a parent
reintroduces __dict__.
Dunder Methods
__repr__ on every class — must be unambiguous: def __repr__(self) -> str: return f"User(name={self.name!r})".
__str__ only when a human-readable form differs from repr.
__eq__ and __hash__ — if you define __eq__, define __hash__ too (or set __hash__ = None to make
unhashable). Mutable objects should not be hashable.
__bool__ — define when truthiness of instances has meaningful semantics.
__enter__/__exit__ for context manager protocol.
__init_subclass__ for class registration patterns without metaclasses.
__class_getitem__ to make classes subscriptable for generic type hints.
Inheritance
- Composition over inheritance. Use inheritance only for true "is-a" relationships.
- ABCs for interfaces when you control both sides and need enforced implementation:
from abc import ABC, abstractmethod.
- Protocols for duck typing when you don't control implementations.
super() — always use super() (no arguments in 3.x). Never hardcode parent class names.
- MRO awareness. Understand method resolution order in diamond inheritance. When in doubt, avoid multiple
inheritance.
Class Methods and Static Methods
@classmethod for alternative constructors: User.from_dict(data).
@staticmethod for utility functions that don't need class or instance state — but prefer module-level functions
unless the function is logically part of the class's API.
Packaging and Toolchain
pyproject.toml
pyproject.toml is the single source of truth for project metadata, dependencies, tool configuration. Never
setup.py or setup.cfg in new projects.
- Build backend: use
hatchling, flit-core, or setuptools with [build-system] table.
- Dependency specification: pin with
>= lower bound, avoid upper bounds unless genuinely incompatible:
httpx>=0.27.
uv
uv is the preferred Python package manager and environment tool.
uv sync to install dependencies from lock file.
uv add <package> to add dependencies.
uv run <command> to run commands in the project environment.
uv lock to generate/update the lock file.
uv venv to create virtual environments.
uv python install 3.14 to install Python versions.
Project Layout
my-project/
├── pyproject.toml
├── uv.lock
├── src/
│ └── my_package/
│ ├── __init__.py
│ └── ...
└── tests/
├── conftest.py
└── ...
- src layout — package code lives under
src/. Prevents accidental imports from the project root during testing.
__init__.py — keep minimal. Define __all__ for public API. Don't put substantial logic in init files.
Linting and Formatting
ruff for both linting and formatting. Single tool, fast.
ruff check to lint. ruff format to format.
- Configure in
pyproject.toml under [tool.ruff].
See ${CLAUDE_SKILL_DIR}/references/packaging.md for pyproject.toml templates, uv workflows, and dependency management
patterns.
Concurrency
asyncio
async/await for I/O-bound concurrency.
asyncio.TaskGroup (3.11+) for structured concurrency — replaces asyncio.gather() with better error handling.
- Never use
asyncio.gather() in new code — it has inconsistent error semantics. Use TaskGroup instead.
asyncio.run() as the single entry point. Never loop.run_until_complete().
- Cancel via
asyncio.CancelledError — always clean up resources in finally blocks.
threading
concurrent.futures.ThreadPoolExecutor for CPU-light I/O-bound parallel work.
threading.Lock for shared mutable state. Always use with lock: context manager.
- GIL note: in CPython, threads don't achieve true parallelism for CPU-bound work. Use
multiprocessing or
ProcessPoolExecutor for CPU-bound tasks.
- Free-threaded Python (3.13+): when running with
--disable-gil, standard thread-safety practices become critical.
Guard all shared mutable state with locks.
General Rules
- Structured concurrency preferred.
TaskGroup and context managers over bare create_task().
- Never fire-and-forget tasks or threads — always track completion.
- Cancellation must be cooperative. Check for cancellation and clean up.
Logging
logging module over print() for anything beyond quick debugging.
logger = logging.getLogger(__name__) at module level.
- Lazy formatting:
logger.info("User %s logged in", user_id) not logger.info(f"User {user_id} logged in") —
f-string evaluates even when level is disabled.
- Use appropriate levels:
DEBUG for diagnostics, INFO for operational events, WARNING for degraded but
working, ERROR for failures, CRITICAL for system-down.
Application
When writing Python code: apply all conventions silently — don't narrate each rule. If an existing codebase
contradicts a convention, follow the codebase and flag the divergence once.
When reviewing Python code: cite the specific violation and show the fix inline. Don't lecture — state what's wrong
and how to fix it.
Bad: "According to Python best practices, you should use type unions
with the pipe operator instead of Optional..."
Good: "Optional[str] -> str | None"
Code Navigation — LSP Required
A pyright-langserver LSP server is configured for .py and .pyi files. Always use LSP tools for code navigation
instead of Grep or Glob. LSP understands Python's module system, type inference, scope rules, and package boundaries —
text search does not.
Tool Routing
goToDefinition — find where a function/class/variable is defined: resolves imports, re-exports, aliases
findReferences — find all usages of a symbol: scope-aware, no false positives from string matches
hover — get type signature, docs, or return types: instant type info without reading source files
documentSymbol — list all symbols in a file: structured output vs grepping for def/class
workspaceSymbol — find a symbol by name across the project: searches all packages, respects __all__
goToImplementation — find implementations of a Protocol or ABC: knows the type system and structural subtyping
incomingCalls — find what calls a function: precise call graph across module boundaries
outgoingCalls — find what a function calls: structured dependency map
Grep/Glob remain appropriate for: text in comments, string literals, log messages, TODO markers, config values, env
vars, file name patterns, URLs, error message text — anything that isn't a Python identifier.
When spawning subagents for Python codebase exploration, instruct them to use LSP tools. Subagents have access to the
same LSP server.
Toolchain
ruff: single entry point for linting and formatting. Must pass before committing.
ruff check — lint. ruff check --fix — auto-fix.
ruff format — format.
uv: package management, virtual environments, Python version management.
mypy or pyright: static type checking. Configure in pyproject.toml.
Integration
The coding skill governs workflow (discovery, planning, verification); this skill governs Python implementation
choices. The pytest skill governs testing conventions — both are active simultaneously when writing Python tests.
Readability counts. If you read a function twice to understand it, rewrite it once to make it clear.
1---2name: python3description: Python language conventions, modern idioms, and toolchain. Invoke whenever task involves any interaction with Python code — writing, reviewing, refactoring, debugging, or understanding Python projects.4---56# Python78**Readability counts. Explicit is better than implicit. If your code needs a comment to explain its control flow,9restructure it.**1011Python 3.14+ is the baseline. Use modern syntax unconditionally — no backward compatibility with older Python versions12unless the project explicitly requires it.1314## References1516- **[`${CLAUDE_SKILL_DIR}/references/typing.md`]** — Type annotation patterns, generics, overloads, TypeVar, variance:17 full annotation examples, generic class patterns, Protocol implementation, TypeVar usage18- **[`${CLAUDE_SKILL_DIR}/references/packaging.md`]** — Project layout, pyproject.toml, uv, dependency management:19 pyproject.toml templates, uv workflows, src layout, dependency groups, build backends20- **[`${CLAUDE_SKILL_DIR}/references/modules.md`]** — Module system, imports, namespace packages, `__init__.py`: import21 resolution order, circular import fixes, lazy imports, namespace packages22- **[`${CLAUDE_SKILL_DIR}/references/concurrency.md`]** — asyncio, TaskGroup, cancellation, timeouts, threading interop:23 TaskGroup error handling, timeout scopes, cancellation semantics, to_thread, eager task factory2425## Naming2627- **Variables, functions, methods** — snake_case: `user_name`, `fetch_data`28- **Classes, type aliases** — PascalCase: `UserService`, `HttpClient`29- **Constants** — UPPER_SNAKE_CASE: `MAX_RETRIES`, `API_BASE_URL`30- **Modules, packages** — snake_case, short: `user_store`, `auth`31- **Private attributes/methods** — `_` prefix: `_internal_cache`, `_validate()`32- **Name-mangled attributes** — `__` prefix: `__secret` (rarely needed)33- **Type variables** — PascalCase, short: `T`, `KT`, `VT`, `ResponseT`34- **Protocols** — PascalCase, `-able`/`-ible` suffix: `Renderable`, `Serializable`3536- **Descriptive names.** `user_count` not `n`. Short names (`i`, `x`) only in tiny scopes (comprehensions, simple37 lambdas).38- **No redundant context.** `car.make` not `car.car_make`.39- **Boolean names:** `is_`/`has_`/`can_`/`should_` prefix: `is_valid`, `has_access`.40- **Dunder methods are reserved for the data model.** Never invent custom dunder names.41- **Avoid single-character names** outside loop indices, comprehension variables, and well-established conventions (`f`42 for file, `e` for exception, `k`/`v` for key/value).4344## Type Annotations4546Python 3.14+ uses modern annotation syntax natively. No `from __future__ import annotations` needed — all annotations47are evaluated lazily by default.4849### Core Rules5051- **Annotate all public API boundaries** — function signatures, class attributes, module-level variables. Internal code52 often needs fewer annotations; types flow from context.53- **Use built-in generics:** `list[str]`, `dict[str, int]`, `tuple[int, ...]`, `set[float]`. Never import `List`,54 `Dict`, `Tuple`, `Set` from `typing`.55- **Union with `|`:** `str | None`, `int | float`. Never `Optional[X]` or `Union[X, Y]`.56- **`type` statement for aliases:** `type Vector = list[float]`. Not `TypeAlias` annotation.57- **`None` return:** annotate `-> None` on functions that return nothing. Omit return type only on `__init__`.58- **Avoid `Any`** — it disables type checking. Use `object` when you mean "any type but still type-safe." Use `Any` only59 at true interop boundaries with untyped code.6061### Generics6263- **`type` parameter syntax (3.12+):** `class Stack[T]:` and `def first[T](items: list[T]) -> T:` instead of `TypeVar`64 declarations.65- **Constrained type parameters:** `def process[T: (str, bytes)](data: T) -> T:` for a finite set of allowed types.66- **Bounded type parameters:** `def sort[T: Comparable](items: list[T]) -> list[T]:` for upper-bound constraints.67- **Variance is inferred** from usage in 3.12+ generics. No manual `covariant`/`contravariant` flags needed.6869### Protocols (Structural Typing)7071- **Prefer protocols over ABCs** when you don't control the implementing types or when structural compatibility is72 sufficient.73- **`@runtime_checkable`** only when you need `isinstance()` checks — it adds overhead and only validates method74 presence, not signatures.75- **Keep protocols small** — one to three methods. A protocol with many methods is a sign you need an ABC or a concrete76 base class.7778```python79from typing import Protocol, runtime_checkable8081@runtime_checkable82class Renderable(Protocol):83 def render(self) -> str: ...84```8586### Callable Types8788- **`collections.abc.Callable`** for callable annotations: `Callable[[int, str], bool]`.89- **`ParamSpec`** for decorators that preserve signatures:90 `def decorator[**P, R](fn: Callable[P, R]) -> Callable[P, R]:`.91- **Use `Protocol`** for complex callable signatures with keyword arguments or overloads.9293### TypeGuard and TypeIs9495- **`TypeIs`** (3.13+) for narrowing that refines the input type:96 `def is_str_list(val: list[object]) -> TypeIs[list[str]]:`.97- **`TypeGuard`** for narrowing where the output type is unrelated to input:98 `def is_valid_config(data: object) -> TypeGuard[Config]:`.99100See `${CLAUDE_SKILL_DIR}/references/typing.md` for full annotation patterns, generics, overloads, and variance.101102## Data Classes and Structured Data103104### dataclasses105106- **Use `@dataclass` for data containers** — classes that primarily hold data with minimal behavior.107- **`frozen=True`** for immutable data: `@dataclass(frozen=True)`. Default to frozen unless mutation is required.108- **`slots=True`** for memory efficiency and attribute safety: `@dataclass(slots=True, frozen=True)`.109- **`kw_only=True`** when constructors have more than 3 fields — prevents positional argument ordering bugs.110- **`field(default_factory=list)`** for mutable defaults. Never use mutable default arguments.111- **Post-init processing:** `__post_init__` for derived fields and validation.112113```python114@dataclass(frozen=True, slots=True, kw_only=True)115class User:116 name: str117 email: str118 roles: list[str] = field(default_factory=list)119```120121### When NOT to Use dataclasses122123- **Simple value containers** with 1-2 fields: use `NamedTuple` or plain tuples.124- **Config/settings with validation:** use Pydantic or attrs with validators.125- **Persistence/ORM models:** use the ORM's model base class.126127### NamedTuple128129- **Use `class` syntax** over functional form: `class Point(NamedTuple): x: float; y: float`.130- NamedTuples are immutable and iterable — useful as dict keys and in destructuring.131132## Enums133134- **Use `enum.Enum`** for categorical constants. Never use bare strings or ints as pseudo-enums.135- **`enum.StrEnum`** when the enum must interoperate with string APIs (JSON, config keys).136- **`enum.IntEnum`** only when integer interop is mandatory (legacy protocols). Prefer `Enum` otherwise.137- **`@enum.unique`** to prevent duplicate values.138- **Access by value:** `Color(1)`. Access by name: `Color["RED"]`. Iteration: `for c in Color:`.139- **Never subclass enums with members.** Enums with members are final.140141```python142from enum import StrEnum, unique143144@unique145class Status(StrEnum):146 ACTIVE = "active"147 INACTIVE = "inactive"148 SUSPENDED = "suspended"149```150151## Pattern Matching152153`match`/`case` (3.10+) is the preferred dispatch mechanism for structural patterns.154155- **Use match for structural dispatch** — matching on type, shape, or destructured values. Don't use match as a156 substitute for simple `if`/`elif` chains on a single value.157- **Always include a wildcard `case _:` arm** unless the match is provably exhaustive.158- **Guard clauses** with `if`: `case Point(x, y) if x > 0:`.159- **Use `|` for alternatives:** `case "quit" | "exit" | "q":`.160- **Capture with walrus:** `case {"error": str() as msg}:` captures while matching type.161- **Class patterns** require `__match_args__` or keyword patterns: `case Point(x=0, y=y):`.162163```python164match command:165 case {"action": "move", "direction": str() as direction}:166 move(direction)167 case {"action": "attack", "target": str() as target}:168 attack(target)169 case _:170 raise ValueError(f"Unknown command: {command}")171```172173## Functions174175- **Early return.** Guard clauses first, happy path flat. Reduce nesting.176- **One function, one job.** If the name contains "and", split it.177- **Type-annotate all parameters and return types** on public functions.178- **Default arguments:** immutable values only. Use `None` + conditional for mutable defaults:179 `def f(items: list[int] | None = None):` then `items = items or []` in body. Never `def f(items: list[int] = []):`.180- **`*` to force keyword-only arguments** after positional params: `def connect(host: str, *, port: int = 443):`.181- **`/` to force positional-only** for parameters that callers shouldn't name: `def sqrt(x: float, /) -> float:`.182- **Prefer returning values** over mutating arguments. Functions should be referentially transparent when possible.183- **`None` means absent, not error.** Return `T | None` for optional results. Raise exceptions for errors.184185## Decorators186187- **Preserve signatures** with `functools.wraps`:188 ```python189 def retry[**P, R](fn: Callable[P, R]) -> Callable[P, R]:190 @functools.wraps(fn)191 def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:192 ...193 return wrapper194 ```195- **Decorator order matters.** Decorators apply bottom-up. `@staticmethod` and `@classmethod` must be outermost (topmost196 in source).197- **Parametric decorators** return a decorator: `@retry(attempts=3)` means `retry` returns the actual decorator198 function.199- **Don't over-abstract with decorators.** If the decorator hides important control flow (error handling, transaction200 management), make it explicit instead.201202## Context Managers203204- **`contextlib.contextmanager`** for simple resource management:205 ```python206 @contextmanager207 def managed_connection(url: str) -> Iterator[Connection]:208 conn = Connection(url)209 try:210 yield conn211 finally:212 conn.close()213 ```214- **Class-based context managers** when state management is complex — implement `__enter__` and `__exit__`.215- **`contextlib.suppress(ExceptionType)`** instead of empty `except: pass`.216- **`contextlib.closing(thing)`** for objects with `.close()` but no `__exit__`.217- **`contextlib.asynccontextmanager`** for async resource management.218- **Always use `with`** for files, locks, database connections, and any resource that needs deterministic cleanup.219220## Generators and Iterators221222- **Generators for lazy sequences.** Use `yield` to produce values on demand instead of building full lists in memory.223- **Generator expressions** over list comprehensions when the result is iterated only once:224 `sum(x * x for x in range(1000))`.225- **`yield from`** to delegate to sub-generators — preserves `.send()`, `.throw()`, `.close()` protocol.226- **`itertools`** for composition: `chain`, `islice`, `groupby`, `batched` (3.12+), `pairwise` (3.10+).227- **Annotate generators:** `def gen() -> Iterator[int]:` for simple generators,228 `Generator[YieldType, SendType, ReturnType]` when using `.send()`.229- **Never exhaust a generator twice.** Generators are single-pass. If you need multiple passes, materialize to a list or230 use `itertools.tee`.231232## Comprehensions233234- **List/dict/set comprehensions** for simple transforms: `[x.name for x in users if x.active]`.235- **One level of nesting maximum.** Two nested `for` clauses are the absolute limit. Beyond that, extract to a function.236- **Don't use comprehensions for side effects.** `[print(x) for x in items]` is wrong — use a `for` loop.237- **Walrus operator in comprehensions** for compute-once-filter-and-use:238 `[y for x in data if (y := transform(x)) is not None]`.239- **Dict comprehensions** for key transformations: `{k.lower(): v for k, v in headers.items()}`.240241## Exception Handling242243- **Be specific.** Catch the narrowest exception type: `except ValueError:` not `except Exception:`.244- **Never bare `except:`.** It catches `SystemExit`, `KeyboardInterrupt`, and `GeneratorExit`. At minimum use245 `except Exception:`.246- **`except* ExceptionGroup`** (3.11+) for handling multiple concurrent exceptions from `TaskGroup` and similar.247- **Wrap with context.** `raise AppError("context") from err` chains the original cause.248- **Don't use exceptions for flow control.** `if key in dict:` not `try: dict[key] except KeyError:` (unless the miss is249 rare and lookup is expensive).250- **Custom exceptions** inherit from a project-specific base that extends `Exception`:251 ```python252 class AppError(Exception): ...253 class NotFoundError(AppError): ...254 class ValidationError(AppError): ...255 ```256- **Error strings:** lowercase, no trailing punctuation. They compose in chains: `"parse config: invalid format"`.257- **`else` clause** runs only when no exception was raised — use for code that should execute on success but isn't part258 of the `try` body.259- **`finally`** for unconditional cleanup — prefer context managers when possible.260- **Exception groups (3.11+):** use `ExceptionGroup` to bundle multiple errors. Handle with `except*` which matches by261 type and re-raises unhandled exceptions.262- **Add notes with `.add_note()`** (3.11+) to attach context without creating new exception types.263264## Strings265266- **f-strings** for interpolation. Never `%` formatting or `.format()` in new code.267- **f-string expressions must be simple.** No function calls with multiple arguments, no nested f-strings, no complex268 expressions. Extract to a variable first.269- **`str.removeprefix()` / `str.removesuffix()`** (3.9+) over slicing.270- **Triple-quoted strings** for multiline. Use `textwrap.dedent` when indentation matters.271- **`"".join(parts)`** for building strings in loops — never `+=` in a loop.272- **Raw strings `r"..."`** for regex patterns and Windows paths.273274## Pathlib275276- **`pathlib.Path`** for all filesystem operations. Never `os.path` in new code.277- **`/` operator** for path joining: `base / "subdir" / "file.txt"`.278- **Common operations:** `path.exists()`, `path.is_file()`, `path.is_dir()`, `path.read_text()`, `path.write_text()`,279 `path.mkdir(parents=True, exist_ok=True)`, `path.iterdir()`, `path.glob("*.py")`, `path.rglob("**/*.py")`.280- **`path.resolve()`** for absolute paths. `path.relative_to(base)` for relative paths.281- **Accept `str | Path`** in public APIs, convert to `Path` internally.282283## Imports284285- **Absolute imports** by default: `from mypackage.utils import helper`.286- **Relative imports** only within packages for tightly coupled modules: `from .models import User`.287- **Import grouping** (separated by blank lines):288 1. Standard library (`import os`, `from pathlib import Path`)289 2. Third-party (`import httpx`, `from pydantic import BaseModel`)290 3. Local (`from myapp.models import User`)291- **Import specific names:** `from collections import defaultdict` not `import collections` (unless you use many names292 from the module).293- **Never `from module import *`** — pollutes namespace, breaks type checkers, hides dependencies.294- **`if TYPE_CHECKING:` block** for imports used only in annotations — avoids circular imports and runtime overhead. In295 3.14+ with lazy annotations, this is less necessary but still useful for avoiding circular import side effects.296- **Lazy imports** in function bodies when a top-level import would create a circular dependency or when the import is297 expensive and rarely needed.298299## Classes300301### Slots302303- **Always use `__slots__`** on classes that will have many instances — prevents `__dict__` creation, saves memory,304 catches typos in attribute names.305- **`@dataclass(slots=True)`** adds slots automatically.306- **Slots and inheritance:** every class in the hierarchy must declare `__slots__`. Missing slots on a parent307 reintroduces `__dict__`.308309### Dunder Methods310311- **`__repr__`** on every class — must be unambiguous: `def __repr__(self) -> str: return f"User(name={self.name!r})"`.312- **`__str__`** only when a human-readable form differs from repr.313- **`__eq__` and `__hash__`** — if you define `__eq__`, define `__hash__` too (or set `__hash__ = None` to make314 unhashable). Mutable objects should not be hashable.315- **`__bool__`** — define when truthiness of instances has meaningful semantics.316- **`__enter__`/`__exit__`** for context manager protocol.317- **`__init_subclass__`** for class registration patterns without metaclasses.318- **`__class_getitem__`** to make classes subscriptable for generic type hints.319320### Inheritance321322- **Composition over inheritance.** Use inheritance only for true "is-a" relationships.323- **ABCs for interfaces** when you control both sides and need enforced implementation:324 `from abc import ABC, abstractmethod`.325- **Protocols for duck typing** when you don't control implementations.326- **`super()`** — always use `super()` (no arguments in 3.x). Never hardcode parent class names.327- **MRO awareness.** Understand method resolution order in diamond inheritance. When in doubt, avoid multiple328 inheritance.329330### Class Methods and Static Methods331332- **`@classmethod`** for alternative constructors: `User.from_dict(data)`.333- **`@staticmethod`** for utility functions that don't need class or instance state — but prefer module-level functions334 unless the function is logically part of the class's API.335336## Packaging and Toolchain337338### pyproject.toml339340- **`pyproject.toml` is the single source of truth** for project metadata, dependencies, tool configuration. Never341 `setup.py` or `setup.cfg` in new projects.342- **Build backend:** use `hatchling`, `flit-core`, or `setuptools` with `[build-system]` table.343- **Dependency specification:** pin with `>=` lower bound, avoid upper bounds unless genuinely incompatible:344 `httpx>=0.27`.345346### uv347348- **`uv`** is the preferred Python package manager and environment tool.349- **`uv sync`** to install dependencies from lock file.350- **`uv add <package>`** to add dependencies.351- **`uv run <command>`** to run commands in the project environment.352- **`uv lock`** to generate/update the lock file.353- **`uv venv`** to create virtual environments.354- **`uv python install 3.14`** to install Python versions.355356### Project Layout357358```359my-project/360├── pyproject.toml361├── uv.lock362├── src/363│ └── my_package/364│ ├── __init__.py365│ └── ...366└── tests/367 ├── conftest.py368 └── ...369```370371- **src layout** — package code lives under `src/`. Prevents accidental imports from the project root during testing.372- **`__init__.py`** — keep minimal. Define `__all__` for public API. Don't put substantial logic in init files.373374### Linting and Formatting375376- **`ruff`** for both linting and formatting. Single tool, fast.377- **`ruff check`** to lint. **`ruff format`** to format.378- **Configure in `pyproject.toml`** under `[tool.ruff]`.379380See `${CLAUDE_SKILL_DIR}/references/packaging.md` for pyproject.toml templates, uv workflows, and dependency management381patterns.382383## Concurrency384385### asyncio386387- **`async`/`await`** for I/O-bound concurrency.388- **`asyncio.TaskGroup`** (3.11+) for structured concurrency — replaces `asyncio.gather()` with better error handling.389- **Never use `asyncio.gather()`** in new code — it has inconsistent error semantics. Use `TaskGroup` instead.390- **`asyncio.run()`** as the single entry point. Never `loop.run_until_complete()`.391- **Cancel via `asyncio.CancelledError`** — always clean up resources in `finally` blocks.392393### threading394395- **`concurrent.futures.ThreadPoolExecutor`** for CPU-light I/O-bound parallel work.396- **`threading.Lock`** for shared mutable state. Always use `with lock:` context manager.397- **GIL note:** in CPython, threads don't achieve true parallelism for CPU-bound work. Use `multiprocessing` or398 `ProcessPoolExecutor` for CPU-bound tasks.399- **Free-threaded Python (3.13+):** when running with `--disable-gil`, standard thread-safety practices become critical.400 Guard all shared mutable state with locks.401402### General Rules403404- **Structured concurrency preferred.** `TaskGroup` and context managers over bare `create_task()`.405- **Never fire-and-forget** tasks or threads — always track completion.406- **Cancellation must be cooperative.** Check for cancellation and clean up.407408## Logging409410- **`logging` module** over `print()` for anything beyond quick debugging.411- **`logger = logging.getLogger(__name__)`** at module level.412- **Lazy formatting:** `logger.info("User %s logged in", user_id)` not `logger.info(f"User {user_id} logged in")` —413 f-string evaluates even when level is disabled.414- **Use appropriate levels:** `DEBUG` for diagnostics, `INFO` for operational events, `WARNING` for degraded but415 working, `ERROR` for failures, `CRITICAL` for system-down.416417## Application418419When **writing** Python code: apply all conventions silently — don't narrate each rule. If an existing codebase420contradicts a convention, follow the codebase and flag the divergence once.421422When **reviewing** Python code: cite the specific violation and show the fix inline. Don't lecture — state what's wrong423and how to fix it.424425```426Bad: "According to Python best practices, you should use type unions427 with the pipe operator instead of Optional..."428Good: "Optional[str] -> str | None"429```430431## Code Navigation — LSP Required432433A `pyright-langserver` LSP server is configured for `.py` and `.pyi` files. **Always use LSP tools for code navigation434instead of Grep or Glob.** LSP understands Python's module system, type inference, scope rules, and package boundaries —435text search does not.436437### Tool Routing438439- **`goToDefinition`** — find where a function/class/variable is defined: resolves imports, re-exports, aliases440- **`findReferences`** — find all usages of a symbol: scope-aware, no false positives from string matches441- **`hover`** — get type signature, docs, or return types: instant type info without reading source files442- **`documentSymbol`** — list all symbols in a file: structured output vs grepping for `def`/`class`443- **`workspaceSymbol`** — find a symbol by name across the project: searches all packages, respects `__all__`444- **`goToImplementation`** — find implementations of a Protocol or ABC: knows the type system and structural subtyping445- **`incomingCalls`** — find what calls a function: precise call graph across module boundaries446- **`outgoingCalls`** — find what a function calls: structured dependency map447448**Grep/Glob remain appropriate for:** text in comments, string literals, log messages, TODO markers, config values, env449vars, file name patterns, URLs, error message text — anything that isn't a Python identifier.450451When spawning subagents for Python codebase exploration, instruct them to use LSP tools. Subagents have access to the452same LSP server.453454## Toolchain455456- **`ruff`**: single entry point for linting and formatting. Must pass before committing.457 - `ruff check` — lint. `ruff check --fix` — auto-fix.458 - `ruff format` — format.459- **`uv`**: package management, virtual environments, Python version management.460- **`mypy` or `pyright`**: static type checking. Configure in `pyproject.toml`.461462## Integration463464The **coding** skill governs workflow (discovery, planning, verification); this skill governs Python implementation465choices. The **pytest** skill governs testing conventions — both are active simultaneously when writing Python tests.466467**Readability counts. If you read a function twice to understand it, rewrite it once to make it clear.**