5 Best Ways to Sort a Tuple of Custom Objects by Properties in Python

πŸ’‘ Problem Formulation: When working with tuples of custom objects in Python, often there is a need to sort these objects not by their intrinsic value, but by their properties or attributes. For instance, given a tuple of employee objects, a typical requirement might be to sort them by an attribute like age or name. This article explores various methods to achieve this, providing a clear demonstration with an example of a tuple of ‘Employee’ objects that should be sorted by ‘age’.

Method 1: Using the sorted() function and a lambda expression

This method involves using Python’s built-in sorted() function, which can take a key argument that allows for custom sorting. A lambda function can directly access an object’s attribute, providing a sorting key that is applied to each object in the tuple.

Here’s an example:

# Define the Employee class
class Employee:
    def __init__(self, name, age):
        self.name = name
        self.age = age

# Tuple of Employee objects
employees = (Employee('Alice', 30), Employee('Bob', 25), Employee('Charlie', 35))

# Sort by age
sorted_employees = tuple(sorted(employees, key=lambda e: e.age))

# Display the results
for employee in sorted_employees:
    print(f'{employee.name}, {employee.age}')

Output:

Bob, 25
Alice, 30
Charlie, 35

The snippet defines a simple Employee class, creates a tuple of such objects, and then sorts them by the age attribute using lambda as a sorting key. The result is a new tuple with employees sorted in ascending order of their ages.

Method 2: Defining a __lt__() method

One can define a custom comparison method, such as __lt__() (less than), within the class to control how objects are compared during sorting.

Here’s an example:

# Modify the Employee class to include comparison
class Employee:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __lt__(self, other):
        return self.age < other.age

# Tuple of Employee objects
employees = (Employee('Alice', 30), Employee('Bob', 25), Employee('Charlie', 35))

# Sort by using the object's own comparison method
sorted_employees = tuple(sorted(employees))

# Display the results
for employee in sorted_employees:
    print(f'{employee.name}, {employee.age}')

Output:

Bob, 25
Alice, 30
Charlie, 35

In this code example, the Employee class has been modified to include a custom __lt__() method. Sorted simply uses the class’s own comparison logic to sort the employees without passing a separate key argument.

Method 3: Using the attrgetter from the operator module

Python’s operator module offers the attrgetter function, which is similar in concept to the lambda function used in Method 1 but can be more efficient and readable.

Here’s an example:

from operator import attrgetter

# Assume Employee class and employees tuple have been defined as before

# Use attrgetter to sort by age
sorted_employees = tuple(sorted(employees, key=attrgetter('age')))

# Display the results
for employee in sorted_employees:
    print(f'{employee.name}: {employee.age}')

Output:

Bob: 25
Alice: 30
Charlie: 35

Here, attrgetter('age') is used as the key for sorting. It creates a callable that extracts the age attribute from an Employee instance, allowing sorted() to order the tuple elements as intended.

Method 4: Using custom sort function

For more complex sorting logic, a separate sorting function can be defined and passed to sorted() as a key.

Here’s an example:

# Assume Employee class and employees tuple have been defined as before

# Custom sort function
def get_age(employee):
    return employee.age

# Sort using the custom function
sorted_employees = tuple(sorted(employees, key=get_age))

# Display the results
for employee in sorted_employees:
    print(f'{employee.name}: {employee.age}')

Output:

Bob: 25
Alice: 30
Charlie: 35

The custom sort function get_age() simply returns the age attribute, which is used by sorted() as the comparison key. This is similar to using a lambda expression but can be more clear when reusability or complex logic is needed.

Bonus One-Liner Method 5: Using sorted() with a list comprehension

While it’s not as neat as the other methods, a combination of list comprehension and sorting can be used for quick one-off tasks. This method is also good for in-place modifications as it doesn’t require the creation of custom classes or functions.

Here’s an example:

# Assume Employee class and employees tuple have been defined as before

# Sort using list comprehension
sorted_employees = tuple(sorted(employees, key=lambda e: (e.age)))

# Display the results
for employee in sorted_employees:
    print(f'{employee.name}: {employee.age}')

Output:

Bob: 25
Alice: 30
Charlie: 35

This one-liner uses the same sorting technique as Method 1, but emphasized for those who prefer concise code. The lambda function is a simple and efficient way to sort a collection based on a single attribute.

Summary/Discussion

  • Method 1: Using sorted() and lambda. Quick and concise. Best for simple cases where defining extra methods or importing modules is not necessary.
  • Method 2: Defining a __lt__() method. Good for objects where you’ll frequently need to sort based on a single attribute. However, you have to modify the class definition.
  • Method 3: Using attrgetter. Can be more readable and expressive than a lambda, particularly when sorting by multiple fields. Requires an import from the operator module.
  • Method 4: Custom sort function. Most appropriate when sorting involves complex logic or for improved code readability. Less succinct than other methods.
  • Method 5: List comprehension and sorted(). Compact code for quick tasks. Less readable when complexity increases and not recommended for complex sorting.