Sorting Custom Keys

Deep dive · part of Python Lists

Use sorted(seq, key=...) with a callable that returns the value to compare. Sorting is stable, so secondary criteria can be handled by sorting multiple times in reverse priority order, or by returning a tuple key.

sorted(iterable, key=callable) computes a sort key per item without mutating originals. Keys can be attributes, tuple priorities, or derived metrics. Python's sort is stable, so tie-breaking can use earlier sorts or compound tuple keys.

operator.itemgetter and attrgetter offer faster C-implemented keys for homogeneous records. For dataclasses, key=lambda x: x.price stays readable; for dict rows, itemgetter('dept') shines.

Production code combines this topic with logging, tests, and clear module boundaries so refactors stay safe when requirements grow.

key receives each element; return value must be comparable (often tuple for multi-field).

Stable sort preserves relative order of equal keys—sort twice for secondary keys (reverse priority) or use tuple keys.

sorted copies; list.sort sorts in place with same key= semantics.

None sorts before other values in Python 3; watch nullable fields.

reverse=True flips comparison after key extraction.

bisect module maintains sorted lists with insort(key=...) for incremental inserts.

Expensive keys run once per element in Timsort—still avoid heavy I/O inside key functions. Precompute sort keys as fields when sorting the same objects repeatedly in hot loops.

Locale-aware sorting needs locale.strxfrm keys, not plain lower(), for human-facing lists.

Sparse JSON keys need .get with sentinel in key=lambda to avoid KeyError during sort of heterogeneous records.

Use .get with sentinel in lambda keys for sparse JSON rows missing optional fields.

Read the parent tutorial on pythondeck.com for runnable snippets, then reproduce them locally in a virtual environment with pinned dependency versions matching your deployment target.

When pairing with teammates, agree on one idiomatic pattern per concern—mixed styles in one repo slow reviews and invite subtle integration bugs during merges.

Sorting twice when one tuple key (dept, -age) would suffice.

Using cmp= removed in Python 3—use key or functools.cmp_to_key.

Non-comparable keys (mixing str and int) raising TypeError mid-sort.

Mutating objects inside key functions as side effects.

Use tuple keys for multi-level business sorting rules.

Prefer attrgetter on named objects for speed and clarity.

Document nullable field ordering explicitly in API specs.

Test sort with equal keys to rely on stability when needed.

Re-read the examples below with these ideas in mind; change variable names and inputs to match your own project.

The program below demonstrates sort by attr. Read the comments on each line, run the code, then change names or values to see how the output shifts.

# Example: Sort by attr
# Run in the REPL or save as a .py file and execute with python.
from dataclasses import dataclass
@dataclass
class Item:
    name: str
    price: float

items = [Item("pen", 1.5), Item("book", 10), Item("ink", 4)]
for it in sorted(items, key=lambda x: x.price):
    print(it)

This sample walks through compound key in a small, runnable script. Paste it into the REPL or save it as a .py file before you continue to the next block.

# Example: Compound key
# Run in the REPL or save as a .py file and execute with python.
people = [
    {"name": "Ada",   "dept": "R&D", "age": 36},
    {"name": "Linus", "dept": "R&D", "age": 54},
    {"name": "Grace", "dept": "OPS", "age": 85},
]
for p in sorted(people, key=lambda p: (p["dept"], -p["age"])):
    print(p)

Here is a hands-on illustration of operator.itemgetter / attrgetter. Follow the inline comments first; only then execute the snippet and compare the result with what you expected.

# Example: operator.itemgetter / attrgetter
# Run in the REPL or save as a .py file and execute with python.
from operator import itemgetter, attrgetter
rows = [(3, "c"), (1, "a"), (2, "b")]
print(sorted(rows, key=itemgetter(1)))

The program below demonstrates attr sort key. Read the comments on each line, run the code, then change names or values to see how the output shifts.

# key= lambda or attrgetter tells sorted how to compare
from dataclasses import dataclass  # tiny records

@dataclass  # auto __init__
class Item:  # row
    name: str  # field
    price: float  # field

items = [Item("pen", 1.5), Item("book", 10), Item("ink", 4)]  # data
for it in sorted(items, key=lambda x: x.price):  # cheap -> expensive
    print(it.name, it.price)  # ordered output

This sample walks through tuple key in a small, runnable script. Paste it into the REPL or save it as a .py file before you continue to the next block.

# Return tuple from key to sort by multiple criteria
people = [  # rows
    {"name": "Ada", "dept": "R&D", "age": 36},  # record
    {"name": "Linus", "dept": "R&D", "age": 54},  # record
    {"name": "Grace", "dept": "OPS", "age": 85},  # record
]  # list end
for p in sorted(people, key=lambda r: (r["dept"], -r["age"])):  # dept asc, age desc
    print(p["name"], p["dept"], p["age"])  # sorted rows

« back to Python Lists All tutorials