Comparison
Comparison with other tools¶
pyreorder is one of several Python source-organisation tools. This page positions
it against the alternatives so you can pick the right tool for your codebase.
At a glance¶
| Tool | Scope | Strategy | Format-preserving | Touches class bodies | Touches imports |
|---|---|---|---|---|---|
| pyreorder | Whole module | Configurable (keep / alpha / stepdown / abstraction) | Yes (libcst) | Yes | No (forward-reference aware) |
| isort | Imports only | Alphabetical by module, with case/known-first-party config | Yes | No | Yes |
Ruff I001 |
Imports only | Same engine as isort | Yes | No | Yes |
| undersort | Class bodies only | Method category (dunder / init / public / private / …) | Yes | Yes | No |
| ssort | Whole module | Topological sort of top-level + class members | Yes | Yes | No |
| sdsort | Whole module | Step-down rule (callers precede callees) | Yes | Yes | No |
| ABSort | Whole module | Topological sort on abstraction-level DAG (Zhang-Shasha) | No | Yes | No |
How they differ¶
Imports: isort and Ruff¶
isort and Ruff's I001 rule do one thing and do it well: sort import
statements. They group by source (stdlib / third-party / local), alphabetise
within each group, and dedupe. They do not look at anything outside the
imports block.
pyreorder does not sort imports — it leaves the imports block alone. The
rationale is that isort / Ruff already do this job perfectly, and combining
two tools that both touch the same lines leads to merge conflicts and
unpredictable diffs. Use both: pyreorder for everything except imports,
isort / Ruff for the imports themselves.
Class bodies: undersort¶
undersort orders methods inside a class: dunders first, then __init__,
then public methods, then private methods. It does not touch anything
outside the class body.
pyreorder includes the same method ordering as part of its pipeline
(sorters.MethodSorter), so if you already use pyreorder you do not need
undersort. The legacy [tool.undersort] table in pyproject.toml is
honoured as a fallback when [tool.pyreorder.class_methods] is absent — see
Configuration.
Whole module: ssort, sdsort, ABSort¶
These three tools each take a different stance on statement ordering:
ssortruns a topological sort on dependencies. Statement A stays before statement B iff A does not depend on B. Within classes, attributes are pinned in their original order, then lifecycle methods, then regular methods in dependency order, then other dunders in a fixed order.sdsortapplies the step-down rule: callers precede callees. The "high-level logic" of a module (the entry point) sits at the top, helpers below.ABSortranks statements by abstraction level (topological sort on the strongly connected components of the dependency graph), with optional Zhang-Shasha-based tie-breaking that reorders statements at the same abstraction level by AST similarity.
pyreorder's stepdown and abstraction strategies overlap with sdsort and
ABSort respectively. The differences are practical:
pyreorder |
ssort / sdsort / ABSort |
|
|---|---|---|
| Section ordering | Configurable: imports → constants → runtime setup → functions → classes → main | Single global order (always topological) |
| Barriers | Forward-reference aware; unrecognised statements stay put | No barriers — every statement is sortable |
| Formatting | libcst round-trip is byte-identical | libcst / native CST; some tools normalise whitespace |
| Configuration | One TOML file; per-section strategies; per-class method ordering | Tool-specific flags / config |
| Class-body sort | Yes (configurable, with undersort fallback) |
Yes (ssort has fixed lifecycle order; ABSort ranks by AST similarity) |
| Imports | No — delegated to isort / Ruff |
No — same |
| Safety stance | "leave unfamiliar code alone" | "reorder everything it can see" |
When to pick pyreorder¶
pyreorder is the right tool when at least two of these are true:
- Your module has both a top-level structure (imports, constants, runtime setup, functions, classes) and class bodies that benefit from a fixed method order.
- You want to delegate import sorting to
isort/ Ruff and have a single tool own the rest of the file. - You have code that
pyreordercannot classify — third-party decorators, conditional__all__updates, runtime-generated class attributes — and you want those statements to stay where they are. - You care that reformatting the file with
pyreorderdoes not introduce whitespace, quote-style, or comment-position changes.
When to pick something else¶
- Just imports? Use
isortor Ruff'sI001.pyreorderwill not touch them. - Just class bodies?
undersortdoes it in one flag. - Topological correctness is your top priority?
ssortis more thorough: it never breaks a dependency.pyreorderis more conservative — it does not move code it cannot prove is safe to move. - Top-down readability is your top priority?
sdsortmakes the high-level logic sit at the top of every file, automatically. - You want maximal reordering and minimal diff?
ABSortsorts by abstraction level with syntax-tree similarity tie-breaking — minimal diff within an abstraction tier.
Using them together¶
A typical Python project can run all of these in sequence without conflict:
# 1. Reorganise whole module (pyreorder)
pyreorder src/
# 2. Sort imports (isort or ruff)
isort src/
# or: ruff check --select I --fix src/
# 3. Format (black or ruff format)
black src/
# or: ruff format src/
pyreorder is designed to be run first because its forward-reference
barriers assume that other tools have not yet moved the imports block. Run
isort and black afterward; their output is independent of the rest of
the file.