PythonPandas.com

How to Sort a List of Custom Objects by an Attribute in Python



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 the people list. The key argument is a lambda function that takes a Person object as input and returns its age. sorted() uses these ages to determine the order of the Person objects 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 the attrgetter function.
  • sorted(people, key=attrgetter('age')): This sorts the people list using attrgetter('age') as the key function. attrgetter('age') creates a callable that fetches the age attribute from each Person object.
  • 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): Adding reverse=True sorts the list in descending order based on the specified key.
  • Both the lambda function and attrgetter methods 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 by age and then, for people with the same age, by name.
  • When using attrgetter('age', 'name'), attrgetter automatically 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. A Person is considered “less than” another if its age is lower.
  • people.sort(): Now, calling people.sort() directly uses the __lt__ method defined in the Person class 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 if person1 should come before person2, 1 if it should come after, and 0 if they are equal.
  • sorted(people, key=cmp_to_key(compare_people)): This uses cmp_to_key to convert the comparison function into a key function that sorted() 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?
You can use the 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?
To sort in descending order, pass the 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?
Yes, you can sort by multiple attributes by providing a tuple to the 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?
Use the __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?
Yes, the 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?
The 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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Post