Python Custom Sorting

August 05, 2024

Overview

On the surface, Custom Sorting in Python can be achieved primarily in two ways. However, if we look closer, we'll find out that both of these ways use the same thing. Furthermore, there's even an alternative, less common, way to achieve custom sorting.

The two mechanisms are:

  • The sorting key function (belonging to sorted() and list.sort()).
  • Less Than (__lt__) function.

Table of Contents

Sorting Key Function
Less Than (__lt__) Function
Other
Conclusion

Sorting Key Function
^

The sorting key function provides a versatile way to control the sorting order in Python iterables. By using the optional key parameter, you can specify a function that returns a value for comparison. This value must be an object that implements a __lt__(self, other) function, which defines the 'less than' behavior for comparisons (e.g. sorting).

This method is particularly useful when working with complex objects or when the sorting criteria involves derived or computed values. For instance, if you have a list of custom objects and you want to sort them based on a specific attribute, the key function approach is straightforward and elegant. It abstracts the comparison logic, making your code cleaner and easier to maintain.

Consider a scenario where you have a list of objects representing different entities, each with multiple attributes. By defining a key function, you can easily sort these objects by any attribute without modifying the objects themselves. This flexibility is one of the main advantages of using the key function for sorting.

class CustomObject:
    def __init__(self, value):
        self.value = value  # value must be a type that has `__lt__` implemented.

# List of objects to sort
objects = [CustomObject(3), CustomObject(1), CustomObject(2)]

# Sorting using a key function
sorted_objects = sorted(objects, key=lambda obj: obj.value)

Less Than (__lt__) Function
^

The __lt__ function, also known as the 'less than' method, is a fundamental part of Python's object model. It is used by many built-in functions that rely on object comparison to complete their tasks. The list of built-in Python functions that rely on __lt__ includes:

  • min function
  • max function
  • sorted function
  • list.sort function
  • heapq module
  • bisect module

Defining a custom __lt__ method within your class allows you to control the natural ordering of your objects. This method should return True if the object is considered less than the other object, and False otherwise. This approach is ideal when you want to encapsulate the comparison logic within the class, promoting encapsulation and making the class self-contained.

For example, if you have a class representing a complex data structure, defining the __lt__ method ensures that all instances of the class can be compared directly. This is particularly useful for operations like sorting, finding the minimum or maximum values, and more.

class CustomObject:
    def __init__(self, value):
        self.value = value

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

# List of objects to sort
objects = [CustomObject(3), CustomObject(1), CustomObject(2)]

# Sorting using the custom __lt__ method
sorted_objects = sorted(objects)

When implementing a __lt__ method, it's common to implement the other comparison methods:

  • __eq__: ==
  • __ne__: !=
  • __gt__: >
  • __le__: <=
  • __ge__: >=

Other
^

For completeness, it's important to mention Python's built-in functools.cmp_to_key function. This function converts the classic comparison function behavior, common in other languages like Java, to a function that can be used as the key parameter of sorted and list.sort functions. This compatibility layer allows you to reuse traditional comparison logic in Python's modern sorting mechanisms.

The cmp_to_key function is particularly useful when migrating code from other programming languages or when you need to implement complex comparison logic that doesn't fit neatly into a key function. By wrapping your comparison function with cmp_to_key, you can leverage existing code and maintain consistency across different parts of your application.

from functools import cmp_to_key

def compare(a, b):
    if a.value < b.value:
        return -1
    elif a.value > b.value:
        return 1
    else:
        return 0

class CustomObject:
    def __init__(self, value):
        self.value = value

# List of objects to sort
objects = [CustomObject(3), CustomObject(1), CustomObject(2)]

# Sorting using cmp_to_key
sorted_objects = sorted(objects, key=cmp_to_key(compare))

Conclusion
^

The sorted and list.sort functions' key functions are most often utilized to return some value that is of a type that does have the __lt__ function defined, but this may not always be possible, or it may be cleaner given the use case to implement a __lt__ function alongside its counterparts (learn more about Python magic methods).

At the end of the day, a __lt__ function is called (but not necessarily one that is implemented by you).