Sort a list of objects by attribute in Python

By James L.

In Python, there are two main ways to sort a list of custom objects by attribute: using the sorted() function or sort() method with a key argument.

Sorting using sorted() function

You can use the sorted() function along with a custom key function to sort a list of objects by a specific attribute.

Here’s an example:

class MyClass:
    def __init__(self, name, age):
        self.name = name
        self.age = age


# List of objects
my_objects = [
    MyClass("Alice", 30),
    MyClass("Bob", 25),
    MyClass("Charlie", 35)
]

# Sort the list of objects by the 'age' attribute
sorted_objects = sorted(my_objects, key=lambda x: x.age)

# Print sorted objects
for obj in sorted_objects:
    print(f"Name: {obj.name}, Age: {obj.age}")

Output:

Name: Bob, Age: 25
Name: Alice, Age: 30
Name: Charlie, Age: 35  

In this example, we define a class MyClass with attributes name and age. Then, we create a list of MyClass objects (my_objects). We use the sorted() function with a lambda function as the key, where we specify that we want to sort the objects based on their age attribute.

The sorted() function returns a new sorted list without modifying the original list.

Sorting using sort() method

Alternatively, you can use the sort() method to sort a list of objects by attribute. This method modifies the original list.

Here’s an example:

class MyClass:
    def __init__(self, name, age):
        self.name = name
        self.age = age


# List of objects
my_objects = [
    MyClass("Alice", 30),
    MyClass("Bob", 25),
    MyClass("Charlie", 35)
]

# Sort the list of objects by the 'age' attribute
my_objects.sort(key=lambda x: x.age)

# Print sorted objects
for obj in my_objects:
    print(f"Name: {obj.name}, Age: {obj.age}")

Output:

Name: Bob, Age: 25
Name: Alice, Age: 30
Name: Charlie, Age: 35

Sorting in reverse

You can sort a list of objects by attribute in reverse by setting the reverse parameter of the sorted() function to True.

Here’s an example:

class MyClass:
    def __init__(self, name, age):
        self.name = name
        self.age = age


# List of objects
my_objects = [
    MyClass("Alice", 30),
    MyClass("Bob", 25),
    MyClass("Charlie", 35)
]

# Sort the list of objects by the 'age' attribute in reverse
sorted_objects = sorted(my_objects, key=lambda x: x.age, reverse=True)
# Print sorted objects
for obj in sorted_objects:
    print(f"Name: {obj.name}, Age: {obj.age}")

Output:

Name: Charlie, Age: 35
Name: Alice, Age: 30
Name: Bob, Age: 25

Similarly, you can set the reverse parameter of the sort() function to True.

class MyClass:
    def __init__(self, name, age):
        self.name = name
        self.age = age


# List of objects
my_objects = [
    MyClass("Alice", 30),
    MyClass("Bob", 25),
    MyClass("Charlie", 35)
]

# Sort the list of objects by the 'age' attribute in reverse
my_objects.sort(key=lambda x: x.age, reverse=True)

# Print sorted objects
for obj in my_objects:
    print(f"Name: {obj.name}, Age: {obj.age}")

Output:

Name: Charlie, Age: 35
Name: Alice, Age: 30
Name: Bob, Age: 25

Sorting using attrgetter()

You can use the attrgetter() function instead of a lambda function to efficiently access specific elements within an object based on their attributes.

Here’s an example:

from operator import attrgetter


class MyClass:
    def __init__(self, name, age):
        self.name = name
        self.age = age


# List of objects
my_objects = [
    MyClass("Alice", 30),
    MyClass("Bob", 25),
    MyClass("Charlie", 35)
]

# Sort the list of objects by the 'age' attribute
sorted_objects = sorted(my_objects, key=attrgetter("age"))
# Print sorted objects
for obj in sorted_objects:
    print(f"Name: {obj.name}, Age: {obj.age}")

The attrgetter() function is faster than the lambda function for sorting a list. However, the difference in speed is negligible for small lists.

Similarly, for sort() method:

from operator import attrgetter


class MyClass:
    def __init__(self, name, age):
        self.name = name
        self.age = age


# List of objects
my_objects = [
    MyClass("Alice", 30),
    MyClass("Bob", 25),
    MyClass("Charlie", 35)
]

# Sort the list of objects by the 'age' attribute
my_objects.sort(key=attrgetter("age"))

# Print sorted objects
for obj in my_objects:
    print(f"Name: {obj.name}, Age: {obj.age}")

Case-insensitive sorting

Sorting a list of objects by string attribute requires special consideration, especially doing case-insensitive sorting. Words beginning with uppercase letters take precedence over those starting with lowercase letters.

Using lower():

This is the most concise and widely used approach. It converts all characters in the strings to lowercase before sorting. However, it may not handle all Unicode characters and characters with different uppercase and lowercase representations in different locales.

Here’s an example:

class Fruits:
    def __init__(self, name, price):
        self.name = name
        self.price = price


# Create a list of Fruits objects
fruits_objects = [
    Fruits("apple", 20),
    Fruits("Orange", 50),
    Fruits("Banana", 10),
    Fruits("grapes", 30)
]

# Sort the list of Fruits objects alphabetically by name (case-insensitive)
sorted_fruits = sorted(fruits_objects, key=lambda x: x.name.lower())

# Print names and prices of sorted fruits
for fruit in sorted_fruits:
    print(f"Name: {fruit.name}, Price: {fruit.price}")

Output:

Name: apple, Price: 20
Name: Banana, Price: 10
Name: grapes, Price: 30
Name: Orange, Price: 50

Similary, for sort() method:

class Fruits:
    def __init__(self, name, price):
        self.name = name
        self.price = price


# Create a list of Fruits objects
fruits_objects = [
    Fruits("apple", 20),
    Fruits("Orange", 50),
    Fruits("Banana", 10),
    Fruits("grapes", 30)
]

# Sort the list of Fruits objects alphabetically by name (case-insensitive)
fruits_objects.sort(key=lambda x: x.name.lower())

# Print names and prices of sorted fruits
for fruit in fruits_objects:
    print(f"Name: {fruit.name}, Price: {fruit.price}")

(b) Using casefold():

This method is more robust than lower() for case-insensitive sorting, as it handles a broader range of characters and special cases. It provides better support for Unicode characters, making it more suitable for internationalization.

Here’s an example:

class Fruits:
    def __init__(self, name, price):
        self.name = name
        self.price = price


# Create a list of Fruits objects
fruits_objects = [
    Fruits("apple", 20),
    Fruits("Orange", 50),
    Fruits("🍉Watermelon", 40),
    Fruits("grapes", 30)
]

# Sort the list of Fruits objects alphabetically by name (case-insensitive)
sorted_fruits = sorted(fruits_objects, key=lambda x: x.name.casefold())

# Print names and prices of sorted fruits
for fruit in sorted_fruits:
    print(f"Name: {fruit.name}, Price: {fruit.price}")

Output:

Name: apple, Price: 20
Name: grapes, Price: 30
Name: Orange, Price: 50
Name: 🍉Watermelon, Price: 40

Similarly, for sort() method:

class Fruits:
    def __init__(self, name, price):
        self.name = name
        self.price = price


# Create a list of Fruits objects
fruits_objects = [
    Fruits("apple", 20),
    Fruits("Orange", 50),
    Fruits("🍉Watermelon", 40),
    Fruits("grapes", 30)
]

# Sort the list of Fruits objects alphabetically by name (case-insensitive)
fruits_objects.sort(key=lambda x: x.name.casefold())

# Print names and prices of sorted fruits
for fruit in fruits_objects:
    print(f"Name: {fruit.name}, Price: {fruit.price}")

(c) Using locale.strxfrm():

This method is the most complex but also the most powerful. It uses the current locale to perform a locale-aware case-insensitive sort. This is useful if you need to sort strings based on their specific cultural context.

Here’s an example:

import locale


class Fruits:
    def __init__(self, name, price):
        self.name = name
        self.price = price


# Set the locale to the user's default
locale.setlocale(locale.LC_ALL, '')

# Create a list of Fruits objects
fruits_objects = [
    Fruits("apple", 20),
    Fruits("Orange", 50),
    Fruits("äpple", 100),
    Fruits("grapes", 30)
]

# Sort the list of Fruits objects alphabetically by name (case-insensitive)
sorted_fruits = sorted(fruits_objects, key=lambda x: locale.strxfrm(x.name))

# Print names and prices of sorted fruits
for fruit in sorted_fruits:
    print(f"Name: {fruit.name}, Price: {fruit.price}")

Output:

Name: apple, Price: 20
Name: äpple, Price: 100
Name: grapes, Price: 30
Name: Orange, Price: 50

Similary, for sort() method:

import locale


class Fruits:
    def __init__(self, name, price):
        self.name = name
        self.price = price


# Set the locale to the user's default
locale.setlocale(locale.LC_ALL, '')

# Create a list of Fruits objects
fruits_objects = [
    Fruits("apple", 20),
    Fruits("Orange", 50),
    Fruits("äpple", 100),
    Fruits("grapes", 30)
]

# Sort the list of Fruits objects alphabetically by name (case-insensitive)
fruits_objects.sort(key=lambda x: locale.strxfrm(x.name))

# Print names and prices of sorted fruits
for fruit in fruits_objects:
    print(f"Name: {fruit.name}, Price: {fruit.price}")

Choosing the best method:

Ultimately, the best choice depends on your specific needs and priorities.

Use lower() if:

  • You are confident your text is purely ASCII without special characters or accented letters.
  • You prioritize performance and simplicity.

Use casefold() if:

  • You are working with Unicode text that might contain special characters or accented letters.
  • You want to ensure consistent results across different languages and locales.

It is less efficient than lower() due to the additional processing required to handle more complex cases.

Use locale.strxfrm if:

  • You need language-specific sorting based on cultural conventions.

The result may vary across different systems due to differences in locale settings. It might be overkill for basic case-insensitive sorting.

Here’s a table summarizing the key differences:

Featurelower()casefold()locale.strxfrm()
Character setASCIIUnicodeUnicode
Special charactersLimited handlingComprehensive handlingComprehensive handling
PerformanceFasterSlightly slowerDepends on locale and complexity
Locale awarenessNoYes (depends on locale settings)Yes (dependent on locale settings)

Differences between sorted() and sort()

The sorted() function and sort() method are both used for sorting elements in a Python list, but they have some key differences:

(1) Return type:

  • sorted(): Returns a new sorted list from the elements of the iterable (list, tuple, or string). The original iterable remains unchanged.
  • sort(): Sorts the elements of the list in-place and returns None. The original list is modified.

(2) Mutability:

  • sorted(): Creates a new sorted list without modifying the original iterable.
  • sort(): Modifies the original list in-place.

(3) Usage:

  • sorted(): It can be used with any iterable (list, tuple, or string), not just lists. It returns a new sorted list regardless of the type of the original iterable.
  • sort(): Applies only to lists. It is a method of the list class and can only be used with lists.

(4) Performance:

The sort() method is generally faster than the sorted() function because it modifies the list in-place, avoiding the overhead of creating a new list.

Here’s a table summarizing the key differences:

Featuresorted()sort()
Return ValueNew sorted listNone
MutabilityNon-mutatingMutates original list
UsageAny iterable objectLists only
PerformanceGenerally slowerGenerally faster

Error handling

Here are some common errors you may encounter when sorting list of objects by attribute in Python and how to handle them (handling error is similar for both sorted() and sort()):

TypeError:

This error occurs if you try to sort a list of objects by attribute of different data types that cannot be compared (e.g., integers and strings).

Check the data types before sorting and convert them if necessary. You can use a lambda function to handle such cases by providing a default value or converting the values to a comparable type.

Here’s an example:

class Fruits:
    def __init__(self, name, price):
        self.name = name
        self.price = price


# Create a list of Fruits objects
fruits_objects = [
    Fruits("apple", 20),
    Fruits("banana", "ten"),
    Fruits("orange", 50),
    Fruits("grapes", 30)
]


try:
    # Sort the list of Fruits objects by price attribute
    sorted_fruits = sorted(fruits_objects, key=lambda x: x.price)

    # Print names and prices of sorted fruits
    for fruit in sorted_fruits:
        print(f"Name: {fruit.name}, Price: {fruit.price}")

except TypeError:
    print("TypeError: Fruits object's 'price' attribute is not comparable")

# Output: TypeError: Fruits object's 'price' attribute is not comparable  

AttributeError:

This error occurs if you try to access an attribute that does not exist in one or more objects in the list.

Ensure that all objects in the list have the same attributes before sorting. You can use a default value or handle missing attributes gracefully using error handling constructs like try-except blocks.

Here’s an example:

class Fruits:
    def __init__(self, name, price):
        self.name = name
        self.price = price


# Create a list of Fruits objects
fruits_objects = [
    Fruits("apple", 20),
    Fruits("banana", 10),
    Fruits("orange", 50),
    Fruits("grapes", 30)
]

# Sort the list of Fruits objects by a non-existing attribute
try:
    sorted_fruits = sorted(fruits_objects, key=lambda x: x.weight)
    # Print names and prices of sorted fruits
    for fruit in sorted_fruits:
        print(f"Name: {fruit.name}, Price: {fruit.price}")
except AttributeError:
    print("AttributeError: Fruits object doesn't contain the specified attribute")

# Output: AttributeError: Fruits object doesn't contain the specified attribute

Conclusion

In this comprehensive guide, we explored various methods and considerations for sorting lists of objects by attribute in Python. From using the built-in sorted() function and sort() method to handling case-insensitive sorting and different data types, you are now equipped to tackle various sorting challenges effectively.

Remember these key takeaways:

Choice of method:

  • Use sorted() for a new sorted list and sort() to modify the original list.
  • Use sort() if performance is a major concern.

Case-insensitive sorting:

  • Use lower() for simple ASCII text, casefold() for broader Unicode support, and locale.strxfrm for locale-specific sorting.

Error Handling:

  • Anticipate potential errors like type mismatches and missing attributes, and implement graceful error handling mechanisms.

By understanding these concepts and applying the techniques covered in this guide, you can confidently conquer list sorting tasks in your Python projects!