Using Python sorted key for Custom Sort Order
python sorted key: Learn how to use the key parameter in Python's sorted() to sort lists, dictionaries, and objects by custom criteria with lambda, itemgetter, and att...
python sorted key requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
The key parameter of Python's sorted() function is the standard way to control sort order when the default comparison of items isn't what you need. Instead of comparing the items themselves, sorted() calls the key function on each item and sorts by the returned value. This is a core feature for working with lists of dictionaries, objects, or any data where the sort criterion is a derived property.
How the key Parameter Works in sorted()
When you call sorted(iterable, key=callable), Python internally creates a list of (key_value, original_item) pairs, sorts that list, and then returns the original items in the sorted order. The key function is called exactly once per item, and its return value is used for all comparisons. This means the key function should be pure and side-effect free; it should not modify the input or rely on external state that changes during the sort.
words = ["banana", "apple", "cherry", "date"] sorted_words = sorted(words, key=len) print(sorted_words) # ['date', 'apple', 'banana', 'cherry']
Here, len is the key function. Each word is replaced by its length, and the list is sorted by those integer values. The original strings are returned, not the lengths. This separation is what makes key flexible: you can sort by any attribute or computed value without changing the items.
The same parameter exists on list.sort(), which sorts the list in place. Both functions share the same semantics for key, reverse, and stability.
Using lambda Functions for Simple Keys
A lambda function is often the most direct way to define a key inline, especially when the transformation is simple and used only once. For example, sorting a list of tuples by the second element:
data = [(1, 'z'), (3, 'a'), (2, 'm')] sorted_data = sorted(data, key=lambda x: x[1]) print(sorted_data) # [(3, 'a'), (2, 'm'), (1, 'z')]
The lambda receives each tuple and returns the element at index 1. This avoids writing a separate named function when the logic is short. However, lambdas are limited to a single expression. If the key logic requires multiple statements or complex branching, define a proper function instead.
Another common use is sorting strings case-insensitively:
names = ["alice", "Bob", "charlie", "David"] sorted_names = sorted(names, key=lambda s: s.lower()) print(sorted_names) # ['alice', 'Bob', 'charlie', 'David']
The lowercased string is used for comparison, but the original casing is preserved in the output.
Sorting by Object Attributes with operator.attrgetter
When sorting a list of custom objects, you often want to sort by a specific attribute. Writing a lambda like lambda obj: obj.attribute works, but the operator module provides attrgetter, which is more readable and slightly faster because it avoids the extra function call overhead.
from operator import attrgetter class Task: def __init__(self, name, priority): self.name = name self.priority = priority def __repr__(self): return f"Task({self.name!r}, {self.priority})" tasks = [Task("write report", 2), Task("fix bug", 1), Task("deploy", 3)] sorted_tasks = sorted(tasks, key=attrgetter("priority")) print(sorted_tasks) # [Task('fix bug', 1), Task('write report', 2), Task('deploy', 3)]
attrgetter also supports multiple attributes. When given several names, it returns a tuple of those attribute values, which is useful for sorting by several fields in a specific order. The same applies to itemgetter for dictionaries and sequences.
Sorting by Dictionary Values and Multiple Keys
For a list of dictionaries, operator.itemgetter is the idiomatic choice. It works on any object that supports __getitem__, including dictionaries and lists.
from operator import itemgetter records = [ {"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}, {"name": "Charlie", "age": 35} ] by_age = sorted(records, key=itemgetter("age")) print(by_age) # [{'name': 'Bob', 'age': 25}, {'name': 'Alice', 'age': 30}, {'name': 'Charlie', 'age': 35}]
When you need to sort by multiple keys, pass multiple arguments to itemgetter or attrgetter. The resulting tuple is compared lexicographically: the first element is the primary sort key, the second breaks ties, and so on.
users = [ {"name": "Alice", "age": 30, "score": 88}, {"name": "Bob", "age": 25, "score": 92}, {"name": "Charlie", "age": 30, "score": 85} ] sorted_users = sorted(users, key=itemgetter("age", "score")) print(sorted_users) # [{'name': 'Bob', 'age': 25, 'score': 92}, {'name': 'Charlie', 'age': 30, 'score': 85}, {'name': 'Alice', 'age': 30, 'score': 88}]
Here, users are first sorted by age, and within the same age, by score ascending. If you need descending order for one field and ascending for another, you cannot use a simple reverse=True because it reverses the entire sort. Instead, negate numeric values or use a custom comparison function via functools.cmp_to_key when the logic is too complex for a key function.
Reverse Order and Sort Stability
Python's sort is stable, meaning that when two items have equal keys, their original relative order is preserved. This property is useful when you perform multiple sorts to establish a primary and secondary order. For example, to sort by score descending and then by name alphabetically, you can sort by name first, then by score with reverse=True.
students = [ {"name": "Alice", "score": 88}, {"name": "Bob", "score": 92}, {"name": "Charlie", "score": 88} ] # First sort by name ascending students.sort(key=itemgetter("name")) # Then sort by score descending; stability keeps name order for ties students.sort(key=itemgetter("score"), reverse=True) print(students) # [{'name': 'Bob', 'score': 92}, {'name': 'Alice', 'score': 88}, {'name': 'Charlie', 'score': 88}]
The reverse parameter only reverses the final order; it does not invert the key function. If you need descending order on a numeric key, you can also use key=lambda x: -x for numbers, but this does not work for strings or other non-numeric types.
Performance Considerations for Key Functions
The key function is called exactly once per item, so its runtime cost is proportional to the size of the iterable. This is far more efficient than a comparison function that is called O(n log n) times. Therefore, you should always prefer a key function over cmp_to_key unless the sort logic genuinely cannot be expressed as a single derived value.
Choosing the right key implementation matters. operator.itemgetter and attrgetter are implemented in C and are faster than an equivalent lambda because they avoid Python-level function call overhead for each item. For large lists, this difference can be measurable. If you are sorting thousands of records, using itemgetter instead of a lambda is a low-effort optimization.
Another performance consideration is memory. sorted() creates a new list, while list.sort() sorts in place and uses less memory when you don't need the original order. If memory is a concern, prefer list.sort() when possible.
Common Mistakes and How to Avoid Them
One frequent mistake is using the key function to modify the items instead of returning a sort key. For example, key=lambda x: x.sort() will sort each element in place and return None, which is almost never what you want. The key function must return a value that can be compared, not perform side effects.
Another issue is forgetting that the key function is applied to the items, not to the container. When sorting a list of strings by their length, key=len works because len is a function. But passing key=len() would call len immediately with no argument and raise an error. The key parameter expects a callable, not a value.
A subtle problem arises when the key function returns a value that is not comparable across items, such as mixing None with integers. Python 3 does not allow comparing None with an integer. You must ensure the key function returns a consistent type, or handle missing values explicitly, for example by using a default value like key=lambda x: x.get("age") or 0.
Finally, be careful when using reverse=True with multiple keys. As noted, it reverses the entire sort, not individual keys. If you need mixed ascending and descending order, either negate numeric values or use functools.cmp_to_key with a custom comparator. The latter is slower but gives full control over the comparison logic.