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

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

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

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']

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
| Approach | Changes the list? | Best for |
|---|---|---|
sorted(items, key=lambda x: x.a) | No, returns a new list | One-off and computed keys |
items.sort(key=...) | Yes, returns None | When the old order isn’t needed |
key=attrgetter("a", "b") | Either | Several or nested attributes, names from strings |
key=lambda x: (x.a, -x.b) | Either | Mixed ascending and descending numbers |
@dataclass(order=True) / __lt__ | Either | Classes 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:
- Sort a dictionary by value in Python
- Python dictionary sort examples
- Dataclass vs namedtuple in Python
- Sorting algorithms in Python
Frequently asked questions
How do I sort a list of objects by attribute in Python?
Use sorted(items, key=lambda x: x.attribute) for a new list, or items.sort(key=lambda x: x.attribute) to sort in place.
How do I sort objects in descending order?
Add reverse=True, for example sorted(staff, key=attrgetter("salary"), reverse=True).
Is attrgetter faster than a lambda?
It’s usually a little faster because it runs in C, but the gap is small. Pick whichever reads better.
How do I sort by two attributes in opposite directions?
Negate the numeric one in a tuple key, key=lambda e: (e.state, -e.salary), or run two stable sorts, secondary key first.
Why do I get “‘<‘ not supported between instances”?
You called sorted() without a key on objects that don’t define __lt__. Pass a key or add @dataclass(order=True).
Where is sorting with key functions documented?
See the Sorting Techniques HOWTO in the Python docs.

Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn