Sorting a list of custom objects based on an attribute is a common task in Python.
This article provides a detailed guide on different methods to achieve this, covering techniques like using the sorted() function with a lambda expression, the attrgetter() function from the operator module, and implementing comparison methods within the custom class.
Let’s say you have a class Person with attributes like name and age. The goal is to sort a list of Person objects by their age. Here’s a sample list:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f"Person(name='{self.name}', age={self.age})"
people = [
Person("Alice", 30),
Person("Bob", 20),
Person("Charlie", 25),
Person("David", 35),
]
print(people)
[Person(name='Alice', age=30), Person(name='Bob', age=20), Person(name='Charlie', age=25), Person(name='David', age=35)]
Method 1: Using sorted() with a lambda Function
The sorted() function in Python is a versatile tool for sorting any iterable. When dealing with custom objects, you can provide a key argument, which specifies a function that extracts a comparison key from each element. A lambda function is perfect for concisely defining this key extraction.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f"Person(name='{self.name}', age={self.age})"
people = [
Person("Alice", 30),
Person("Bob", 20),
Person("Charlie", 25),
Person("David", 35),
]
sorted_people = sorted(people, key=lambda person: person.age)
print(sorted_people)
[Person(name='Bob', age=20), Person(name='Charlie', age=25), Person(name='Alice', age=30), Person(name='David', age=35)]
Explanation:
sorted(people, key=lambda person: person.age): This line sorts thepeoplelist. Thekeyargument is alambdafunction that takes aPersonobject as input and returns itsage.sorted()uses these ages to determine the order of thePersonobjects in the sorted list.- The output shows that the list is now sorted in ascending order of age.
Method 2: Using attrgetter() from the operator Module
The operator module provides functions that offer efficient attribute and item lookups. The attrgetter() function is particularly useful for sorting objects based on attributes. It avoids the need to write a lambda function, making the code more readable.
from operator import attrgetter
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f"Person(name='{self.name}', age={self.age})"
people = [
Person("Alice", 30),
Person("Bob", 20),
Person("Charlie", 25),
Person("David", 35),
]
sorted_people = sorted(people, key=attrgetter('age'))
print(sorted_people)
[Person(name='Bob', age=20), Person(name='Charlie', age=25), Person(name='Alice', age=30), Person(name='David', age=35)]
Explanation:
from operator import attrgetter: This imports theattrgetterfunction.sorted(people, key=attrgetter('age')): This sorts thepeoplelist usingattrgetter('age')as the key function.attrgetter('age')creates a callable that fetches theageattribute from eachPersonobject.- The output is the same as in Method 1, demonstrating that the list is sorted by age.
Method 3: Sorting in Descending Order
To sort in descending order, you can use the reverse=True argument in the sorted() function. This works seamlessly with both lambda functions and attrgetter().
from operator import attrgetter
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f"Person(name='{self.name}', age={self.age})"
people = [
Person("Alice", 30),
Person("Bob", 20),
Person("Charlie", 25),
Person("David", 35),
]
# Using lambda function
sorted_people_desc_lambda = sorted(people, key=lambda person: person.age, reverse=True)
print("Sorted using lambda (descending):", sorted_people_desc_lambda)
# Using attrgetter
sorted_people_desc_attrgetter = sorted(people, key=attrgetter('age'), reverse=True)
print("Sorted using attrgetter (descending):", sorted_people_desc_attrgetter)
Sorted using lambda (descending): [Person(name='David', age=35), Person(name='Alice', age=30), Person(name='Charlie', age=25), Person(name='Bob', age=20)] Sorted using attrgetter (descending): [Person(name='David', age=35), Person(name='Alice', age=30), Person(name='Charlie', age=25), Person(name='Bob', age=20)]
Explanation:
sorted(..., reverse=True): Addingreverse=Truesorts the list in descending order based on the specified key.- Both the
lambdafunction andattrgettermethods produce the same result: a list sorted by age from oldest to youngest.
Method 4: Sorting by Multiple Attributes
You can sort by multiple attributes by returning a tuple from the key function. Python sorts tuples lexicographically, meaning it compares the first elements, then the second elements if the first ones are equal, and so on.
from operator import attrgetter
class Person:
def __init__(self, name, age, city):
self.name = name
self.age = age
self.city = city
def __repr__(self):
return f"Person(name='{self.name}', age={self.age}, city='{self.city}')"
people = [
Person("Alice", 30, "New York"),
Person("Bob", 20, "Los Angeles"),
Person("Charlie", 25, "New York"),
Person("David", 30, "Chicago"),
]
# Sort by age and then by name
sorted_people = sorted(people, key=lambda person: (person.age, person.name))
print(sorted_people)
# Sort by age and then by name using attrgetter
sorted_people_attrgetter = sorted(people, key=attrgetter('age', 'name'))
print(sorted_people_attrgetter)
[Person(name='Bob', age=20, city='Los Angeles'), Person(name='Charlie', age=25, city='New York'), Person(name='Alice', age=30, city='New York'), Person(name='David', age=30, city='Chicago')] [Person(name='Bob', age=20, city='Los Angeles'), Person(name='Charlie', age=25, city='New York'), Person(name='Alice', age=30, city='New York'), Person(name='David', age=30, city='Chicago')]
Explanation:
sorted(people, key=lambda person: (person.age, person.name)): This sorts first byageand then, for people with the same age, byname.- When using
attrgetter('age', 'name'),attrgetterautomatically creates a tuple for comparison. - Notice that Alice and David, both 30, are now sorted alphabetically by their names.
Method 5: Using __lt__ Method (Less Than)
For more complex sorting logic or when the sorting criteria are intrinsic to the object itself, you can define the __lt__() (less than) method within your custom class. This method allows you to define how two instances of your class should be compared.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f"Person(name='{self.name}', age={self.age})"
def __lt__(self, other):
return self.age < other.age
people = [
Person("Alice", 30),
Person("Bob", 20),
Person("Charlie", 25),
Person("David", 35),
]
people.sort() # Uses the __lt__ method for comparison
print(people)
[Person(name='Bob', age=20), Person(name='Charlie', age=25), Person(name='Alice', age=30), Person(name='David', age=35)]
Explanation:
def __lt__(self, other): return self.age < other.age: This defines the less-than comparison. APersonis considered “less than” another if itsageis lower.people.sort(): Now, callingpeople.sort()directly uses the__lt__method defined in thePersonclass to determine the sorting order. This modifies the original list in place.
Method 6: Using cmp_to_key for Custom Comparison Logic (Python 2 Compatible)
While the key argument in sorted() and the __lt__ method are the preferred ways to sort in Python 3, older versions of Python (Python 2) used a comparison function that takes two arguments and returns -1, 0, or 1 based on their relative order. The functools.cmp_to_key function allows you to adapt a comparison function to work with the key argument in sorted(), providing a bridge for legacy code or complex comparison scenarios.
from functools import cmp_to_key
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f"Person(name='{self.name}', age={self.age})"
def compare_people(person1, person2):
if person1.age < person2.age:
return -1
elif person1.age > person2.age:
return 1
else:
return 0
people = [
Person("Alice", 30),
Person("Bob", 20),
Person("Charlie", 25),
Person("David", 35),
]
sorted_people = sorted(people, key=cmp_to_key(compare_people))
print(sorted_people)
[Person(name='Bob', age=20), Person(name='Charlie', age=25), Person(name='Alice', age=30), Person(name='David', age=35)]
Explanation:
compare_people(person1, person2): This function defines the comparison logic. It returns -1 ifperson1should come beforeperson2, 1 if it should come after, and 0 if they are equal.sorted(people, key=cmp_to_key(compare_people)): This usescmp_to_keyto convert the comparison function into a key function thatsorted()can use.- The output shows the list sorted correctly by age.
Frequently Asked Questions
How do I sort a list of objects by a specific attribute in Python?
sorted() function along with a lambda function or the attrgetter() function from the operator module to specify the attribute to sort by. For example: sorted(my_list, key=lambda x: x.attribute) or sorted(my_list, key=attrgetter('attribute')).
What is the difference between sorted() and list.sort()?
sorted() is a built-in function that returns a new sorted list from any iterable, leaving the original iterable unchanged. list.sort() is a method of list objects that sorts the list in place, modifying the original list directly and returning None.
How can I sort a list of objects in descending order?
reverse=True argument to the sorted() function. For example: sorted(my_list, key=lambda x: x.attribute, reverse=True).
Can I sort objects by multiple attributes?
key argument. The sorting will be performed lexicographically based on the attributes in the tuple. For example: sorted(my_list, key=lambda x: (x.attribute1, x.attribute2)).
How does attrgetter() work?
attrgetter() from the operator module creates a callable object that retrieves the given attribute(s) from its operand. It’s often used as the key argument in sorted() to specify which attribute to use for sorting in a more readable way than using a lambda function.
When should I use the __lt__() method for sorting?
__lt__() method when you want to define a natural ordering for your custom objects. By implementing this method, you can directly use list.sort() on a list of your objects without providing a key function. This is useful when the sorting logic is intrinsic to the object itself.
Is it possible to sort a list of custom objects without modifying the original list?
sorted() function returns a new sorted list without modifying the original list. If you want to sort the list in place (modifying the original), you can use the list.sort() method.
What is cmp_to_key and when should I use it?
cmp_to_key function (from the functools module) is used to convert a comparison function (which takes two arguments and returns -1, 0, or 1) into a key function that can be used with sorted(). It’s primarily useful for compatibility with older Python versions (Python 2) where comparison functions were more common or for complex comparison logic that’s easier to express as a comparison function.