Python List Sort: Using sort() and sorted() Effectively
python list sort: Learn how to sort Python lists with sort() and sorted(), including key functions, reverse order, stability, and performance considerations.
When you need to order a Python list, the sort() method and the sorted() function are the two primary tools. Both rely on the same underlying sorting algorithm, but they differ in how they handle the original list and what they return. Understanding these differences is the first step to writing clear, efficient code for python list sort operations.
The Difference Between sort() and sorted()
The sort() method is called directly on a list and modifies that list in place. It returns None, so any attempt to assign its result to a variable will silently create a None value instead of a sorted list. This is a common source of bugs for developers new to Python.
numbers = [3, 1, 4, 1, 5] numbers.sort() print(numbers) # [1, 1, 3, 4, 5]
The sorted() function, on the other hand, accepts any iterable and returns a new list containing the sorted elements. The original iterable remains unchanged. This makes sorted() the safer choice when you need to preserve the original order or when you are working with a tuple, string, or generator.
numbers = [3, 1, 4, 1, 5] sorted_numbers = sorted(numbers) print(numbers) # [3, 1, 4, 1, 5] unchanged print(sorted_numbers) # [1, 1, 3, 4, 5]
Use sort() when you want to sort a list in place and do not need the original order. Use sorted() when you need a new sorted list or when the input is not a list.
Sorting with the key Parameter
Both sort() and sorted() accept a key parameter that specifies a function to be called on each element before comparison. This is the most flexible way to control sorting behavior, and it is essential for sorting lists of dictionaries, objects, or any data where the natural comparison is not what you need.
For example, to sort a list of strings by their length:
words = ["banana", "apple", "cherry", "date"] words.sort(key=len) print(words) # ['date', 'apple', 'banana', 'cherry']
The key function is applied exactly once per element, and the sorted order is determined by the values it returns. This is more efficient than a custom comparison function because the key values are computed once and then cached internally.
When the key logic is more complex, a lambda function is often used. For example, sorting a list of tuples by the second element:
pairs = [(1, 'one'), (3, 'three'), (2, 'two')] pairs.sort(key=lambda pair: pair[1]) print(pairs) # [(1, 'one'), (3, 'three'), (2, 'two')] # sorted by the string
For dictionary keys or attribute access, operator.itemgetter and operator.attrgetter are cleaner and faster than lambdas because they are implemented in C and avoid Python function call overhead.
from operator import itemgetter people = [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}] people.sort(key=itemgetter("age")) print(people) # [{'name': 'Bob', 'age': 25}, {'name': 'Alice', 'age': 30}]
Sorting in Reverse Order
To sort in descending order, set the reverse parameter to True. This works for both sort() and sorted(), and it applies after the key function has been evaluated.
numbers = [5, 2, 8, 1] numbers.sort(reverse=True) print(numbers) # [8, 5, 2, 1]
Be careful not to confuse reverse=True with reversing the list after sorting. Sorting with reverse=True produces a true descending sort, while calling reverse() on a sorted list simply flips the order, which is equivalent only when the list is already sorted in ascending order. If the list contains duplicate values, the two approaches can differ in the relative order of equal elements.
Stability and Order Preservation
Python's sorting algorithm is stable, meaning that when two elements compare equal, their original order is preserved. This property is crucial when you need to sort by multiple criteria. Instead of writing a complex key function that combines all criteria, you can sort in multiple passes, each with a different key, and rely on stability to maintain the previous ordering.
For example, to sort a list of tuples by name and then by age, you can sort by age first, then by name:
records = [("Alice", 30), ("Bob", 25), ("Alice", 25), ("Bob", 30)] records.sort(key=lambda r: r[1]) # sort by age records.sort(key=lambda r: r[0]) # stable sort by name print(records) # [('Alice', 25), ('Alice', 30), ('Bob', 25), ('Bob', 30)]
The second sort preserves the age order within each name group because the sort is stable. This technique is often clearer than constructing a composite key like (r[0], r[1]), especially when the criteria have different directions or when you need to sort by a large number of fields.
Performance and Memory Considerations
Both sort() and sorted() use Timsort, a hybrid sorting algorithm derived from merge sort and insertion sort. Timsort runs in O(n log n) time in the worst case and O(n) time when the list is already nearly sorted, making it highly efficient for real-world data that often contains ordered runs.
The main performance difference between the two functions is memory. sort() sorts in place and does not create a copy of the list, so it uses only a small amount of extra memory for temporary storage during the sort. sorted() always creates a new list, which means it doubles the memory footprint for the list of elements. For very large lists, this can be a significant concern.
# In-place sort: minimal extra memory large_list.sort() # Sorted copy: additional memory for the new list new_list = sorted(large_list)
When the key function is expensive, both approaches compute the key once per element and store the results internally. This is why using a simple key function is almost always faster than using a custom cmp function (which Python 3 removed). The key function should be as lightweight as possible; if you need to sort by a computed attribute that is used multiple times, consider precomputing it into a tuple or a dedicated field.
Sorting Dictionaries and Custom Objects
Sorting a dictionary directly is not possible because dictionaries are not sequences. However, you can sort the keys, values, or items of a dictionary by converting them to a list first. A common pattern is to sort a dictionary's items by value:
scores = {"Alice": 90, "Bob": 75, "Charlie": 85} sorted_scores = sorted(scores.items(), key=lambda item: item[1]) print(sorted_scores) # [('Bob', 75), ('Charlie', 85), ('Alice', 90)]
For custom objects, you can define a key function that returns an attribute, or you can implement the __lt__ method to give the class a natural ordering. The latter approach is useful when the sort order is an intrinsic property of the object, but it can be confusing if you need different orderings in different contexts. Prefer an explicit key function unless the object has a single obvious ordering.
class Person: def __init__(self, name, age): self.name = name self.age = age people = [Person("Alice", 30), Person("Bob", 25)] people.sort(key=lambda p: p.age)
Common Pitfalls and Edge Cases
Sorting a list with mixed types raises a TypeError in Python 3 because the comparison operators are not defined between incompatible types. For example, sorting [1, "two", 3] fails. If you need to sort such a list, you must provide a key function that converts elements to a common comparable type, such as a string or a tuple.
Another edge case is handling None values. By default, comparing None with an integer or string raises a TypeError. To sort lists that may contain None, use a key function that maps None to a sentinel value, such as float('inf') to place None at the end.
values = [3, None, 1, None, 2] values.sort(key=lambda x: (x is None, x)) print(values) # [1, 2, 3, None, None]
Case sensitivity is another common issue when sorting strings. The default comparison is case-sensitive, so uppercase letters come before lowercase letters in ASCII order. To sort alphabetically regardless of case, use key=str.lower.
words = ["Banana", "apple", "Cherry"] words.sort(key=str.lower) print(words) # ['apple', 'Banana', 'Cherry']
When to Use Each Approach
The choice between sort() and sorted() often comes down to whether you need to preserve the original list. In a script where the list is a temporary intermediate value, sort() is more memory-efficient and signals that the original order is no longer needed. In a function that must not mutate its input, sorted() is the only correct choice.
For key functions, prefer operator.itemgetter and operator.attrgetter over lambdas when they can express the logic directly. They are more readable and slightly faster. Reserve lambdas for cases where the key logic is a simple expression that cannot be expressed with a built-in operator.
Finally, remember that sorting is a stable operation. Use this property to your advantage when you need multi-criteria sorting, and avoid writing custom comparison functions that re-implement what Timsort already does efficiently. By understanding the behavior of sort() and sorted(), you can handle any python list sort requirement with confidence.