Sorting Your Own Objects: __lt__, order=True, min, max and bisect
18 min · Intermediate
Need help? Ask a tutor on WhatsApp (opens in a new tab)Sorting your own classes needs a rule for which object is smaller. Once you have one, sorted, min and max work on your objects, and bisect finds positions in a sorted list very fast.
sorted needs a less-than rule
Natural ordering is the default order of a class, defined by its __lt__ method. sorted, min and max use it when no key is given.
Sorting invoices fails because Python cannot tell which invoice is smaller. Add __lt__ to give it a rule.
class Invoice: def __init__(self, number, amount): self.number = number self.amount = amount
def __lt__(self, other): return self.amount < other.amount
def __repr__(self): return f"Invoice({self.number!r}, {self.amount})"
invoices = [Invoice("I-1", 900), Invoice("I-2", 150), Invoice("I-3", 400)]print(sorted(invoices))Example 1 · No rule, no sort
class Invoice: def __init__(self, amount): self.amount = amount
try: sorted([Invoice(5), Invoice(2)])except TypeError as error: print(error)'<' not supported between instances of 'Invoice' and 'Invoice'
What this code does
- Without __lt__, Python has no way to compare two Invoice objects.
- The error names the operator and the class, which tells you what to add.
sorted uses only the < operator, so __lt__ is enough.
dataclass order=True
order=True is a dataclass option that writes __lt__, __le__, __gt__ and __ge__. Objects compare field by field in the order the fields are declared, like tuples.
Writing __lt__ for every class is repetitive. A dataclass can write all the comparisons for you from its fields.
from dataclasses import dataclass
@dataclass(order=True)class Ticket: priority: int title: str
tickets = [Ticket(2, "Login fails"), Ticket(1, "Payment error"), Ticket(2, "Add export")]for ticket in sorted(tickets): print(ticket)Example 2 · field(compare=False) leaves a field out
from dataclasses import dataclass, field
@dataclass(order=True)class Task: due_day: int name: str = field(compare=False)
a = Task(3, "Send invoices")b = Task(3, "Close books")print(a < b)print(a == b)print(min(a, b).name)False True Send invoices
What this code does
- The name is ignored in every comparison, so a and b are equal and neither is smaller.
- min returns the first of equal items, so it prints a's name.
Priority is compared first. The title breaks the tie between the two priority 2 tickets.
min and max with key and default
To find the largest order you do not need a full sort. min and max scan the data once and accept the same key argument as sorted.
orders = [("A-1", 250), ("A-2", 40), ("A-3", 900)]print(max(orders, key=lambda order: order[1]))print(min(orders, key=lambda order: order[1]))print(max([], default=0))Example 3 · max and min on a dictionary
marks = {"Asha": 88, "Ravi": 72, "Meena": 95}print(max(marks, key=marks.get))print(min(marks.items(), key=lambda pair: pair[1]))Meena
('Ravi', 72)What this code does
- Looping over a dictionary gives keys, so
max(marks, key=marks.get)returns the name with the highest mark. - With
.items()you get the whole (name, mark) pair.
They cost O(n), cheaper than sorting. On a tie the first item wins. An empty input needs default, or Python raises ValueError.
bisect on a sorted list
bisect is a standard-library module that finds where a value belongs in an already sorted list by binary search in O(log n). insort inserts the value there and keeps the list sorted.
Marks map to grades by cutoffs. bisect finds where a mark falls in the sorted cutoffs without a chain of if statements.
from bisect import bisect_right, insort
cutoffs = [40, 60, 75, 90]grades = "FDCBA"print(grades[bisect_right(cutoffs, 78)])
ids = [101, 105, 110]insort(ids, 107)print(ids)Example 4 · bisect_left and bisect_right with repeats
from bisect import bisect_left, bisect_right
prices = [10, 20, 20, 30]left = bisect_left(prices, 20)right = bisect_right(prices, 20)print(left, right)print(right - left)1 3 2
What this code does
- bisect_left gives the first position where 20 could go, bisect_right the position after the last 20.
- Their difference counts how many 20s the list holds.
bisect_right returns the position, here 3, and grades[3] is B. insort inserts 107 and keeps the list sorted.
Next step
Check your understanding
12 quick questions