How to Sort a List of Objects by Attribute in Python

To sort a list of objects by attribute in Python, pass a key function to sorted(): sorted(items, key=lambda x: x.attr) returns a new list ordered by that attribute. Use items.sort(key=...) to sort in place, and add reverse=True for descending order:

sorted(staff, key=lambda e: e.salary)        # new list, lowest salary first
staff.sort(key=attrgetter("name"))            # in place, A-Z by name

Every output below is real, captured from a Windows Command Prompt running Python 3.12.5.

In this tutorial, we’ll cover lambda keys, attrgetter, multiple attributes, sortable classes, and None values.

Sort a list of objects by attribute with sorted() and a lambda

Here’s an Employee class with a name, state and USD salary. The lambda tells sorted() what to compare:

class Employee:
    def __init__(self, name, state, salary):
        self.name = name
        self.state = state
        self.salary = salary

staff = [
    Employee("Megan Bowen", "WA", 98500.00),
    Employee("Alex Wilber", "TX", 72250.50),
    Employee("Adele Vance", "NY", 115000.00),
    Employee("Lee Gu", "TX", 88900.75),
]

by_salary = sorted(staff, key=lambda e: e.salary)
for e in by_salary:
    print(f"{e.name:<12} {e.state}  ${e.salary:>10,.2f}")

print("staff[0] is still:", staff[0].name)

Output:

Alex Wilber  TX  $ 72,250.50
Lee Gu       TX  $ 88,900.75
Megan Bowen  WA  $ 98,500.00
Adele Vance  NY  $115,000.00
staff[0] is still: Megan Bowen
Command Prompt output of Python sorted() with a lambda key ordering Employee objects by salary from lowest to highest
The employees come back ordered by salary, and the original list keeps its first item.

The key runs once per object, and Python compares the returned values, so the class needs no comparison methods.

Names sort the same way with key=lambda e: e.name; case and accents follow the rules in my guide to sorting a list alphabetically in Python.

Sort the list of objects in place with list.sort()

list.sort() takes the same key and reverse arguments but changes the list itself. It returns None, which trips up a lot of beginners:

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

staff = [Employee("Megan Bowen", 98500.00), Employee("Alex Wilber", 72250.50),
         Employee("Adele Vance", 115000.00), Employee("Lee Gu", 88900.75)]

result = staff.sort(key=lambda e: e.name)
print("sort() returned:", result)
print("A-Z:", [e.name for e in staff])

staff.sort(key=lambda e: e.salary, reverse=True)
print("Highest paid first:", [f"{e.name} ${e.salary:,.2f}" for e in staff[:2]])

Output:

sort() returned: None
A-Z: ['Adele Vance', 'Alex Wilber', 'Lee Gu', 'Megan Bowen']
Highest paid first: ['Adele Vance $115,000.00', 'Megan Bowen $98,500.00']

Never write staff = staff.sort(...), because you’d replace your list with None.

The same new-list versus in-place choice comes up when you filter lists in Python, so the habit pays off twice.

Use operator.attrgetter instead of a lambda

operator.attrgetter builds the key function for you. It accepts several names and dotted paths into nested objects:

from dataclasses import dataclass
from operator import attrgetter

@dataclass
class Address:
    city: str
    zip_code: str

@dataclass
class Customer:
    name: str
    state: str
    address: Address

customers = [
    Customer("Megan Bowen", "WA", Address("Seattle", "98101")),
    Customer("Lee Gu", "TX", Address("Dallas", "75201")),
    Customer("Adele Vance", "NY", Address("Albany", "12207")),
    Customer("Alex Wilber", "TX", Address("Austin", "73301")),
]

print([c.name for c in sorted(customers, key=attrgetter("state"))])
print([c.name for c in sorted(customers, key=attrgetter("state", "name"))])
print([c.address.city for c in sorted(customers, key=attrgetter("address.city"))])

Output:

['Adele Vance', 'Lee Gu', 'Alex Wilber', 'Megan Bowen']
['Adele Vance', 'Alex Wilber', 'Lee Gu', 'Megan Bowen']
['Albany', 'Austin', 'Dallas', 'Seattle']

By state alone, the Texas customers keep their original order; attrgetter("state", "name") returns a tuple, so they’re also ordered by name. attrgetter("address.city") reaches into the nested Address object.

It’s the object version of itemgetter, which you’d use for a Python list of tuples or a list of dictionaries.

How do you sort objects by multiple attributes?

Return a tuple from the key. Python compares the first items and only checks the next one on a tie:

from dataclasses import dataclass
from operator import attrgetter

@dataclass
class Employee:
    name: str
    state: str
    salary: float

staff = [Employee("Megan Bowen", "WA", 98500.00), Employee("Alex Wilber", "TX", 72250.50),
         Employee("Adele Vance", "NY", 115000.00), Employee("Lee Gu", "TX", 88900.75),
         Employee("Nestor Wilke", "WA", 98500.00)]

# State A-Z, then salary high-to-low: negate the number
one_pass = sorted(staff, key=lambda e: (e.state, -e.salary))

# Same order with two stable sorts: secondary key first, primary key last
two_pass = sorted(staff, key=attrgetter("salary"), reverse=True)
two_pass.sort(key=attrgetter("state"))

for e in one_pass:
    print(f"{e.state}  {e.name:<13} ${e.salary:>10,.2f}")
print("Both orders match:", one_pass == two_pass)

Output:

NY  Adele Vance   $115,000.00
TX  Lee Gu        $ 88,900.75
TX  Alex Wilber   $ 72,250.50
WA  Megan Bowen   $ 98,500.00
WA  Nestor Wilke  $ 98,500.00
Both orders match: True
Command Prompt output of Python sorting Employee objects by state ascending and salary descending with a tuple key and with two stable sorts
Employees are grouped by state A-Z with the highest salary first in each state, and both techniques give the same order.

Negating works for numbers only. For a descending string or date inside an ascending sort, use two passes: Python’s sort is stable, so equal items keep the order the first pass gave them.

Sort the least important key first and the main key last. Rows of plain lists follow the same rules, as shown in sorting a list of lists in Python.

Make your objects sortable with __lt__ or @dataclass(order=True)

Without a key, sorted() has to compare the objects themselves, and a plain class doesn’t know how:

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

staff = [Employee("Megan Bowen", 98500.00), Employee("Alex Wilber", 72250.50)]
print(sorted(staff))

Error:

Traceback (most recent call last):
  File "C:\pyguides\runs\sortobjattr\ex_noorder.py", line 7, in <module>
    print(sorted(staff))
          ^^^^^^^^^^^^^
TypeError: '<' not supported between instances of 'Employee' and 'Employee'

If one attribute is the natural order for a class, build it in. @dataclass(order=True) compares fields in the order you declare them, and field(compare=False) leaves fields out:

from dataclasses import dataclass, field

@dataclass(order=True)
class Order:
    total: float                                  # compared first
    order_id: str = field(compare=False)
    customer: str = field(compare=False)

orders = [Order(249.99, "A-1043", "Megan Bowen"), Order(39.50, "A-1044", "Alex Wilber"),
          Order(1200.00, "A-1045", "Adele Vance")]
print([o.order_id for o in sorted(orders)])

class Ticket:
    def __init__(self, ticket_id, priority):
        self.ticket_id = ticket_id
        self.priority = priority

    def __lt__(self, other):                      # sorted() only needs <
        return self.priority < other.priority

tickets = [Ticket("T-310", 3), Ticket("T-311", 1), Ticket("T-312", 2)]
print([t.ticket_id for t in sorted(tickets)])
print("Cheapest order:", min(orders).order_id)

Output:

['A-1044', 'A-1043', 'A-1045']
['T-311', 'T-312', 'T-310']
Cheapest order: A-1044
Command Prompt output of Python sorted() ordering dataclass Order objects by total and Ticket objects by priority through __lt__
Orders sort by their total and tickets by priority, with no key argument at all.

A single __lt__ method is all sorted(), min() and max() need. For more on fields and defaults, read the Python dataclass guide.

Sort objects when an attribute is None or missing

Comparing None with a float raises TypeError. A tuple key fixes it: r.bonus is None is False for real values, so they sort before the None ones:

from dataclasses import dataclass

@dataclass
class Rep:
    name: str
    bonus: float | None

reps = [Rep("Megan Bowen", 1500.00), Rep("Alex Wilber", None),
        Rep("Adele Vance", 2750.00), Rep("Lee Gu", 900.00)]

try:
    sorted(reps, key=lambda r: r.bonus)
except TypeError as err:
    print("TypeError:", err)

# (False, value) sorts before (True, 0): None goes last
ranked = sorted(reps, key=lambda r: (r.bonus is None, r.bonus or 0))
print([(r.name, r.bonus) for r in ranked])

class Temp:
    def __init__(self, name):
        self.name = name

mixed = reps + [Temp("Nestor Wilke")]                 # no bonus attribute at all
print([x.name for x in sorted(mixed, key=lambda x: getattr(x, "bonus", None) or 0, reverse=True)])

Output:

TypeError: '<' not supported between instances of 'NoneType' and 'float'
[('Lee Gu', 900.0), ('Megan Bowen', 1500.0), ('Adele Vance', 2750.0), ('Alex Wilber', None)]
['Adele Vance', 'Megan Bowen', 'Lee Gu', 'Alex Wilber', 'Nestor Wilke']
Command Prompt output of Python TypeError when sorting objects with a None attribute, then a tuple key putting None last and getattr handling a missing attribute
The plain key fails with a TypeError; the tuple key moves the rep without a bonus to the end, and getattr() copes with an object that has no bonus attribute.

getattr(x, "bonus", None) returns a default instead of raising AttributeError. The same key works with the max() function in Python when you want only the top object.

Ways to sort objects by attribute compared

ApproachChanges the list?Best for
sorted(items, key=lambda x: x.a)No, returns a new listOne-off and computed keys
items.sort(key=...)Yes, returns NoneWhen the old order isn’t needed
key=attrgetter("a", "b")EitherSeveral or nested attributes, names from strings
key=lambda x: (x.a, -x.b)EitherMixed ascending and descending numbers
@dataclass(order=True) / __lt__EitherClasses with one natural order

I reach for attrgetter for plain attributes and a lambda once the key needs any logic. To see what the sort itself does step by step, try to sort a list in Python without the sort() function.

Want to keep going with sorting? These pick up where this one stops:

Frequently asked questions