Refactor Python
Purpose
Refactor Python code safely and idiomatically while preserving behavior. Optimize for readable code, simple module boundaries, explicit contracts, useful tests, and maintainable Python. Avoid importing patterns from other ecosystems when simple functions, modules, dataclasses, protocols, or standard-library tools fit better.
Use this skill for cleanup, simplification, renaming, decomposition, dependency untangling, test characterization, typing improvements, packaging cleanup, or async/data-model refactors. Do not treat a rewrite, redesign, feature, or performance project as a refactor unless the user explicitly asks for that scope.
Non-Negotiables
- Preserve behavior. Keep public APIs, CLI flags, exceptions, log messages, file formats, ordering, database schemas, and side effects compatible unless the user approves a breaking change.
- Work from evidence. Read code, tests, config, docs, and call sites before changing design.
- Make one coherent change at a time. Prefer small, reviewable diffs over bundled redesigns.
- Protect risky behavior. Run existing checks; add characterization tests when coverage is missing and the refactor is not trivial.
- Follow the project. Match its Python version, formatter, linter, type checker, test style, package layout, and dependency manager.
- Avoid architecture astronautics. Add abstractions only when duplication, volatility, or dependency direction justifies them.
- Do not hide behavior changes inside cleanup. If you discover a bug, report it or fix it in a separate, explicit change.
First Inspection Checklist
Before editing, identify:
- Tooling/config:
pyproject.toml, uv.lock, .python-version, setup.cfg, tox.ini, noxfile.py, pytest.ini, .pre-commit-config.yaml, CI, or scripts.
- Working tree and recent changes:
git status, git diff, and relevant recent tests when available.
- Supported Python versions from
requires-python, lockfiles, CI, docs, or runtime constraints.
- Test/lint/type commands and whether the repo uses
pytest, unittest, tox, nox, ruff, black, isort, mypy, pyright, basedpyright, or pylint.
- Public contracts: documented imports,
__init__.py, __all__, entry points, CLIs, plugin hooks, serialized data, exceptions, and log output.
- Call sites before renaming, moving, or changing signatures.
- Dependency manager. If the project uses
uv, use that workflow rather than manual virtualenv or pip commands.
Safe Workflow
- Discover baseline. Read relevant code/config and run a focused existing check if practical. Record pre-existing failures.
- Characterize when needed. Add focused tests around current behavior before moving parsing, serialization, CLI behavior, async logic, or bug-prone code.
- Refactor one seam. Rename, extract, inline, move, or simplify one coherent concept.
- Verify. Run the narrowest useful tests/lint/type checks, expanding scope only as warranted.
- Review Python hazards. Inspect the diff for accidental behavior changes; check imports/cycles, public contracts, mutable defaults, exception behavior, async cancellation, encodings, paths, timezone/money precision, and dependency metadata.
- Report clearly. State files changed, checks run, compatibility notes, and remaining risk.
Verification Commands
Use project-defined commands first. Common focused commands:
python -m pytest path/to/test_file.py -q
python -m pytest -q
python -m ruff check .
python -m ruff format --check .
python -m mypy package_or_module
python -m pyright
For uv projects, prefer the locked environment:
uv run pytest path/to/test_file.py -q
uv run ruff check .
uv run ruff format --check .
uv run mypy package_or_module
uv run pyright
uv sync --locked
uv lock --check
Do not run expensive full suites casually. If checks cannot run, explain what was inspected and what remains unverified.
uv Projects
Treat a repo as uv-managed when you see uv.lock, [tool.uv], [tool.uv.workspace], or docs/scripts using uv run, uv sync, uv add, or uv lock.
- Run tools with
uv run <command>.
- Add/remove dependencies with
uv add, uv add --dev, and uv remove.
- Use
uv sync to create/update the environment; use uv sync --locked or uv lock --check to verify without changing the lockfile.
- Do not hand-edit
uv.lock.
- Do not introduce Poetry, Pipenv,
requirements.txt, or manual pip install workflows into a uv project unless the repo already uses them for a specific purpose.
- Keep runtime dependencies in
project.dependencies and dev-only tools in dependency groups.
- When removing imports, remove the corresponding dependency only after checking all code, extras, docs, and entry points.
- Preserve
requires-python; do not add syntax or dependencies incompatible with it.
If uv is not already used, do not migrate to uv as part of a refactor unless asked.
Refactoring Moves
Use the smallest move that clarifies the code:
- Rename confusing modules, functions, variables, types, or concepts after checking call sites.
- Extract cohesive functions/methods/modules from large blocks; pass only required data.
- Inline indirection that no longer earns its name.
- Move behavior closer to the data or dependency it primarily uses.
- Separate pure logic from effects so I/O, network, database, time, randomness, and environment access sit at boundaries.
- Introduce dataclasses/enums/protocols/type aliases only when they clarify invariants or stabilize a boundary.
- Decompose conditionals with guard clauses, named predicates, or dispatch when that is clearer and supported by the Python version.
- Add compatibility re-exports when moving public names that callers may import from old paths.
Example compatibility shim:
# old_module.py
from .new_module import useful_function
__all__ = ["useful_function"]
Smells and Preferred Responses
| Smell |
Prefer |
Avoid |
| Long function |
Extract named, cohesive steps |
Splitting by arbitrary line count |
| Large class/module |
Separate stable responsibilities |
Many tiny anemic wrappers |
| Duplication |
Extract the shared concept after confirming behavior matches |
Abstracting coincidental similarity |
| Confusing names |
Use domain terms from callers/tests/docs |
Cute abbreviations or generic names |
| Long parameter list |
Group cohesive values into a dataclass/config object |
Giant untyped bags of unrelated data |
| Deep nesting |
Guard clauses and named predicates |
Clever control flow that hides branches |
| Primitive obsession |
Enum, Literal, NewType, TypedDict, or dataclasses when they enforce meaning |
Heavy classes for every scalar |
| Hidden side effects |
Make dependencies explicit at boundaries |
Helpers that look pure but perform I/O |
| Circular imports |
Move shared types/helpers lower; use if TYPE_CHECKING: |
Import-time hacks or monkeypatching |
| Dead private code |
Remove it and any tests that only covered it |
Deleting public behavior tests or leaving commented alternatives |
Python-Specific Guidance
Data and state
- Use mutable defaults safely:
None sentinels or dataclasses.field(default_factory=...).
- Prefer dataclasses,
NamedTuple, TypedDict, or existing models for dict-shaped structured data; choose based on mutability and boundary needs.
- Use
slots=True only when supported and safe for public classes; it can affect dynamic attributes, inheritance, and pickling.
- Avoid boilerplate getter/setter classes. Use plain attributes or properties with real invariants.
- Prefer composition, functions, or
typing.Protocol over deep inheritance unless runtime registration or shared implementation is needed.
Imports, modules, and packaging
- Avoid import-time side effects: expensive I/O, network calls, logging configuration, environment mutation, argument parsing, or display/database initialization.
- Move script bodies behind
main() and return exit codes:
def main() -> int:
...
return 0
if __name__ == "__main__":
raise SystemExit(main())
- Preserve documented import paths, entry points, and
__all__.
- Keep CLI/framework adapters and infrastructure separate from domain logic where practical.
Resources and external effects
- Use context managers for files, locks, temporary directories, database sessions, and network clients.
- Prefer
pathlib.Path for new internal path handling unless project style or APIs favor strings.
- Specify encodings for text files when behavior should be stable.
- Make time, randomness, clients, repositories, and configuration explicit dependencies when that improves testability.
Exceptions and errors
- Preserve public exception types/messages during refactors.
- Avoid bare
except: and broad except Exception unless intentionally isolating a boundary.
- Chain exceptions when translating errors:
raise DomainError(...) from exc.
- Do not swallow
asyncio.CancelledError; preserve cancellation behavior.
Typing
Add or improve types when they clarify contracts and the project supports typing.
- Prefer built-in generics (
list[str]) when supported and collections.abc interfaces (Iterable, Sequence, Mapping, Callable) for accepted inputs.
- Use
Protocol for structural boundaries, TypeAlias for repeated complex types, and object instead of Any when callers must narrow.
- Use
Self, override, assert_never, match, StrEnum, or datetime.UTC only when the configured Python/type-checker version supports them, or when the project already uses compatible backports.
- Avoid lying annotations, invasive typing rewrites, unnecessary
cast(), and weakening typed code with Any.
- Separate runtime imports from type-only imports with
if TYPE_CHECKING: when needed to avoid cycles.
Async, precision, and performance-sensitive behavior
- Preserve sync/async public contracts.
- Do not call blocking I/O directly inside async functions; use async libraries or executors.
- Keep event loop creation at application boundaries.
- Use timezone-aware datetimes for real-world timestamps.
- Use
Decimal for money/exact decimal calculations; do not replace it with float during cleanup.
- Do not optimize blindly. Measure first if performance is the goal or a hot path is being changed.
Testing Guidance
- Prefer focused tests that lock current behavior before changing internals.
- Use
pytest fixtures/parametrization, tmp_path, monkeypatch, capsys, and caplog when they fit.
- Use mocks sparingly; assert externally visible behavior rather than implementation details.
- Keep tests deterministic by controlling time, randomness, locale, filesystem, and network.
- Consider property tests for parsers, serializers, validators, and numeric invariants when already in use or clearly valuable.
Anti-Patterns to Avoid
- Refactoring by formatting the whole repository unless requested.
- Mixing unrelated cleanup with feature work or bug fixes.
- Adding design patterns because a conditional exists.
- Turning every function into a class or every scalar into a value object.
- Replacing readable loops with dense comprehensions.
- Introducing optional dependencies for small standard-library tasks.
- Moving code without checking imports, entry points, docs, and compatibility exports.
- Changing public exception/log/message/data behavior casually.
- Hiding mutable configuration in module globals.
Final Response Checklist
When reporting back, include:
- What refactoring was done and why.
- Files changed.
- Tests/checks run and outcomes.
- Compatibility notes for moved or public APIs.
- Follow-up work that should remain separate.
1---2name: python-refactor3description: Standalone Python refactoring skill for behavior-preserving cleanup using Python best practices. Use when refactoring Python packages, scripts, CLIs, tests, async code, data models, or APIs with attention to PEP 8/257, typing, pytest, uv project/dependency management, packaging, imports, resource handling, and idiomatic Python design.4---56# Refactor Python78## Purpose910Refactor Python code safely and idiomatically while preserving behavior. Optimize for readable code, simple module boundaries, explicit contracts, useful tests, and maintainable Python. Avoid importing patterns from other ecosystems when simple functions, modules, dataclasses, protocols, or standard-library tools fit better.1112Use this skill for cleanup, simplification, renaming, decomposition, dependency untangling, test characterization, typing improvements, packaging cleanup, or async/data-model refactors. Do **not** treat a rewrite, redesign, feature, or performance project as a refactor unless the user explicitly asks for that scope.1314## Non-Negotiables15161. **Preserve behavior.** Keep public APIs, CLI flags, exceptions, log messages, file formats, ordering, database schemas, and side effects compatible unless the user approves a breaking change.172. **Work from evidence.** Read code, tests, config, docs, and call sites before changing design.183. **Make one coherent change at a time.** Prefer small, reviewable diffs over bundled redesigns.194. **Protect risky behavior.** Run existing checks; add characterization tests when coverage is missing and the refactor is not trivial.205. **Follow the project.** Match its Python version, formatter, linter, type checker, test style, package layout, and dependency manager.216. **Avoid architecture astronautics.** Add abstractions only when duplication, volatility, or dependency direction justifies them.227. **Do not hide behavior changes inside cleanup.** If you discover a bug, report it or fix it in a separate, explicit change.2324## First Inspection Checklist2526Before editing, identify:2728- Tooling/config: `pyproject.toml`, `uv.lock`, `.python-version`, `setup.cfg`, `tox.ini`, `noxfile.py`, `pytest.ini`, `.pre-commit-config.yaml`, CI, or scripts.29- Working tree and recent changes: `git status`, `git diff`, and relevant recent tests when available.30- Supported Python versions from `requires-python`, lockfiles, CI, docs, or runtime constraints.31- Test/lint/type commands and whether the repo uses `pytest`, `unittest`, `tox`, `nox`, `ruff`, `black`, `isort`, `mypy`, `pyright`, `basedpyright`, or `pylint`.32- Public contracts: documented imports, `__init__.py`, `__all__`, entry points, CLIs, plugin hooks, serialized data, exceptions, and log output.33- Call sites before renaming, moving, or changing signatures.34- Dependency manager. If the project uses `uv`, use that workflow rather than manual virtualenv or `pip` commands.3536## Safe Workflow37381. **Discover baseline.** Read relevant code/config and run a focused existing check if practical. Record pre-existing failures.392. **Characterize when needed.** Add focused tests around current behavior before moving parsing, serialization, CLI behavior, async logic, or bug-prone code.403. **Refactor one seam.** Rename, extract, inline, move, or simplify one coherent concept.414. **Verify.** Run the narrowest useful tests/lint/type checks, expanding scope only as warranted.425. **Review Python hazards.** Inspect the diff for accidental behavior changes; check imports/cycles, public contracts, mutable defaults, exception behavior, async cancellation, encodings, paths, timezone/money precision, and dependency metadata.436. **Report clearly.** State files changed, checks run, compatibility notes, and remaining risk.4445## Verification Commands4647Use project-defined commands first. Common focused commands:4849```bash50python -m pytest path/to/test_file.py -q51python -m pytest -q52python -m ruff check .53python -m ruff format --check .54python -m mypy package_or_module55python -m pyright56```5758For `uv` projects, prefer the locked environment:5960```bash61uv run pytest path/to/test_file.py -q62uv run ruff check .63uv run ruff format --check .64uv run mypy package_or_module65uv run pyright66uv sync --locked67uv lock --check68```6970Do not run expensive full suites casually. If checks cannot run, explain what was inspected and what remains unverified.7172## uv Projects7374Treat a repo as uv-managed when you see `uv.lock`, `[tool.uv]`, `[tool.uv.workspace]`, or docs/scripts using `uv run`, `uv sync`, `uv add`, or `uv lock`.7576- Run tools with `uv run <command>`.77- Add/remove dependencies with `uv add`, `uv add --dev`, and `uv remove`.78- Use `uv sync` to create/update the environment; use `uv sync --locked` or `uv lock --check` to verify without changing the lockfile.79- Do not hand-edit `uv.lock`.80- Do not introduce Poetry, Pipenv, `requirements.txt`, or manual `pip install` workflows into a uv project unless the repo already uses them for a specific purpose.81- Keep runtime dependencies in `project.dependencies` and dev-only tools in dependency groups.82- When removing imports, remove the corresponding dependency only after checking all code, extras, docs, and entry points.83- Preserve `requires-python`; do not add syntax or dependencies incompatible with it.8485If uv is not already used, do not migrate to uv as part of a refactor unless asked.8687## Refactoring Moves8889Use the smallest move that clarifies the code:9091- **Rename** confusing modules, functions, variables, types, or concepts after checking call sites.92- **Extract** cohesive functions/methods/modules from large blocks; pass only required data.93- **Inline** indirection that no longer earns its name.94- **Move** behavior closer to the data or dependency it primarily uses.95- **Separate pure logic from effects** so I/O, network, database, time, randomness, and environment access sit at boundaries.96- **Introduce dataclasses/enums/protocols/type aliases** only when they clarify invariants or stabilize a boundary.97- **Decompose conditionals** with guard clauses, named predicates, or dispatch when that is clearer and supported by the Python version.98- **Add compatibility re-exports** when moving public names that callers may import from old paths.99100Example compatibility shim:101102```python103# old_module.py104from .new_module import useful_function105106__all__ = ["useful_function"]107```108109## Smells and Preferred Responses110111| Smell | Prefer | Avoid |112| --- | --- | --- |113| Long function | Extract named, cohesive steps | Splitting by arbitrary line count |114| Large class/module | Separate stable responsibilities | Many tiny anemic wrappers |115| Duplication | Extract the shared concept after confirming behavior matches | Abstracting coincidental similarity |116| Confusing names | Use domain terms from callers/tests/docs | Cute abbreviations or generic names |117| Long parameter list | Group cohesive values into a dataclass/config object | Giant untyped bags of unrelated data |118| Deep nesting | Guard clauses and named predicates | Clever control flow that hides branches |119| Primitive obsession | `Enum`, `Literal`, `NewType`, `TypedDict`, or dataclasses when they enforce meaning | Heavy classes for every scalar |120| Hidden side effects | Make dependencies explicit at boundaries | Helpers that look pure but perform I/O |121| Circular imports | Move shared types/helpers lower; use `if TYPE_CHECKING:` | Import-time hacks or monkeypatching |122| Dead private code | Remove it and any tests that only covered it | Deleting public behavior tests or leaving commented alternatives |123124## Python-Specific Guidance125126### Data and state127128- Use mutable defaults safely: `None` sentinels or `dataclasses.field(default_factory=...)`.129- Prefer dataclasses, `NamedTuple`, `TypedDict`, or existing models for dict-shaped structured data; choose based on mutability and boundary needs.130- Use `slots=True` only when supported and safe for public classes; it can affect dynamic attributes, inheritance, and pickling.131- Avoid boilerplate getter/setter classes. Use plain attributes or properties with real invariants.132- Prefer composition, functions, or `typing.Protocol` over deep inheritance unless runtime registration or shared implementation is needed.133134### Imports, modules, and packaging135136- Avoid import-time side effects: expensive I/O, network calls, logging configuration, environment mutation, argument parsing, or display/database initialization.137- Move script bodies behind `main()` and return exit codes:138139```python140def main() -> int:141 ...142 return 0143144if __name__ == "__main__":145 raise SystemExit(main())146```147148- Preserve documented import paths, entry points, and `__all__`.149- Keep CLI/framework adapters and infrastructure separate from domain logic where practical.150151### Resources and external effects152153- Use context managers for files, locks, temporary directories, database sessions, and network clients.154- Prefer `pathlib.Path` for new internal path handling unless project style or APIs favor strings.155- Specify encodings for text files when behavior should be stable.156- Make time, randomness, clients, repositories, and configuration explicit dependencies when that improves testability.157158### Exceptions and errors159160- Preserve public exception types/messages during refactors.161- Avoid bare `except:` and broad `except Exception` unless intentionally isolating a boundary.162- Chain exceptions when translating errors: `raise DomainError(...) from exc`.163- Do not swallow `asyncio.CancelledError`; preserve cancellation behavior.164165### Typing166167Add or improve types when they clarify contracts and the project supports typing.168169- Prefer built-in generics (`list[str]`) when supported and `collections.abc` interfaces (`Iterable`, `Sequence`, `Mapping`, `Callable`) for accepted inputs.170- Use `Protocol` for structural boundaries, `TypeAlias` for repeated complex types, and `object` instead of `Any` when callers must narrow.171- Use `Self`, `override`, `assert_never`, `match`, `StrEnum`, or `datetime.UTC` only when the configured Python/type-checker version supports them, or when the project already uses compatible backports.172- Avoid lying annotations, invasive typing rewrites, unnecessary `cast()`, and weakening typed code with `Any`.173- Separate runtime imports from type-only imports with `if TYPE_CHECKING:` when needed to avoid cycles.174175### Async, precision, and performance-sensitive behavior176177- Preserve sync/async public contracts.178- Do not call blocking I/O directly inside async functions; use async libraries or executors.179- Keep event loop creation at application boundaries.180- Use timezone-aware datetimes for real-world timestamps.181- Use `Decimal` for money/exact decimal calculations; do not replace it with `float` during cleanup.182- Do not optimize blindly. Measure first if performance is the goal or a hot path is being changed.183184## Testing Guidance185186- Prefer focused tests that lock current behavior before changing internals.187- Use `pytest` fixtures/parametrization, `tmp_path`, `monkeypatch`, `capsys`, and `caplog` when they fit.188- Use mocks sparingly; assert externally visible behavior rather than implementation details.189- Keep tests deterministic by controlling time, randomness, locale, filesystem, and network.190- Consider property tests for parsers, serializers, validators, and numeric invariants when already in use or clearly valuable.191192## Anti-Patterns to Avoid193194- Refactoring by formatting the whole repository unless requested.195- Mixing unrelated cleanup with feature work or bug fixes.196- Adding design patterns because a conditional exists.197- Turning every function into a class or every scalar into a value object.198- Replacing readable loops with dense comprehensions.199- Introducing optional dependencies for small standard-library tasks.200- Moving code without checking imports, entry points, docs, and compatibility exports.201- Changing public exception/log/message/data behavior casually.202- Hiding mutable configuration in module globals.203204## Final Response Checklist205206When reporting back, include:207208- What refactoring was done and why.209- Files changed.210- Tests/checks run and outcomes.211- Compatibility notes for moved or public APIs.212- Follow-up work that should remain separate.