Skip to content

Sorting modes

Sorting modes

Each section is ordered by a strategy. Set it per section under [tool.pyreorder.strategy], or override on the fly with --strategy-overrides. Four strategies exist:

Value Meaning Applies to
keep Preserve original order (default) any section
alpha Stable alphabetical by primary name imports, enums, constants
stepdown Caller before callee (top-down narrative) functions, classes
abstraction Callee before caller (low-level utilities first) functions, classes

stepdown / abstraction only make sense for functions and classes; applied to other sections they fall back to alpha. Cycles keep their original relative order.

keep

The default. Statements stay exactly where you wrote them; pyreorder only moves them between sections, never reorders within one.

alpha

Stable alphabetical ordering by the statement's primary name — the import module, the enum/class name, or the constant target.

# before
def zebra():
    ...
def apple():
    ...
def mango():
    ...

# after (alpha)
def apple():
    ...
def mango():
    ...
def zebra():
    ...

Caution

alpha is safe for imports and enums, but reordering interdependent module_constants or classes (inheritance, forward references) can raise NameError / MRO errors at runtime. Always preview with pyreorder diff before committing an alpha reordering of constants or classes.

Note: pyreorder now automatically prevents the most common NameError case: a module-level assignment (constant or runtime_setup) whose RHS references a name defined in a later section (e.g. _DEFAULT_COLOR = Color.RED where Color is an enum) is treated as a barrier — it stays in place rather than being hoisted to its section. When from __future__ import annotations is active, annotation-only names in AnnAssign are excluded from this check.

stepdown — caller before callee

The Clean Code step-down rule: a caller appears above the functions it calls, giving a top-down reading order.

# before
def greet(name):
    print(f"hi {name}")

def main():
    greet("world")

def helper():
    ...

# after (functions = "stepdown")
def main():          # top-level caller first
    greet("world")

def greet(name):     # called by main
    print(f"hi {name}")

def helper():        # unused helper last
    ...

abstraction — callee before caller

The reverse: low-level utilities appear first, high-level orchestration last. Useful when you want the building blocks at the top of the file.

# before
def main():
    greet("world")

def greet(name):
    print(f"hi {name}")

# after (functions = "abstraction")
def greet(name):     # leaf utility first
    print(f"hi {name}")

def main():          # orchestrator last
    greet("world")

stepdown vs abstraction at a glance

stepdown abstraction
Reading direction top-down narrative bottom-up construction
Where callers sit above their callees below their callees
Good for onboarding a reader exposing primitives

In-class method ordering (undersort)

Independent of the section strategy, pyreorder reorders methods within each class using undersort semantics — grouped by visibility then method type, stable within each group:

# before
class Service:
    def _internal(self):
        ...
    def start(self):
        ...
    @staticmethod
    def make():
        ...
    def __secret(self):
        ...

# after (order = public, protected, private; method_type = instance, class, static)
class Service:
    def start(self):            # public instance
        ...
    @staticmethod
    def make():         # public static
        ...
    def _internal(self):        # protected instance
        ...
    def __secret(self):         # private instance
        ...

Configure the grouping under [tool.pyreorder.class_methods], or disable it with enabled = false. A class C: # pyreorder: off trailing comment opts a single class out; # nosort works the same. Per-method # nosort (or # pyreorder: off) locks that single method at its original index even when the class is reordered.

For one-off invocations, pass the overrides on the command line:

pyreorder run src/ --class-methods-order private,protected,public
pyreorder run src/ --method-type-order static,instance,class

These resolve on top of the discovered file config and apply to every command (run, check, diff). The legacy [tool.undersort] (or top-level [undersort] in standalone configs) is still read for order and method_type_order when [tool.pyreorder.class_methods] is absent.

Trying it out safely

Always preview changes before writing them. pyreorder diff shows a unified diff without touching the file, and pyreorder check (exit-code based) is ideal for CI or pre-commit hooks:

pyreorder diff src/                            # preview every change
pyreorder run src/ --strategy-overrides functions=stepdown
pyreorder check src/                           # exit 1 if anything would change

Realistic examples

The repository ships with five fully-formed sample modules in tests/data/ that demonstrate every strategy and feature on believable Python code — not toy snippets. Each has an *_unsorted.py input and its committed *_sorted.py output:

Sample Strategy shown Highlights
web_service_{unsorted,sorted}.py stepdown dataclasses, enums, retry loop, undersort on Client
csv_pipeline_{unsorted,sorted}.py stepdown runtime barriers (_VALIDATORS + register_validator), TYPE_CHECKING
cli_app_{unsorted,sorted}.py keep (default) the classic app = typer.Typer() barrier keeping commands together
plugin_registry_{unsorted,sorted}.py abstraction callee-first ordering: leaf utilities before the orchestrator
inventory_models_{unsorted,sorted}.py alpha (enums + functions) rich undersort: all visibility/type combinations in one class

Try them:

pyreorder diff tests/data/plugin_registry_unsorted.py
pyreorder run tests/data/inventory_models_unsorted.py --strategy-overrides enums=alpha,functions=alpha