How to Sort Python Lists Like a Pro: Tips, Tricks, and Best Practices
Master Python list sorting with .sort() and sorted(), custom keys, multi-criteria sorting, and performance tips. This guide covers everything from basics to advanced techniques like natural sorting and stable sorts.
Advertisement
Sorting lists in Python might seem like a basic task, but there's a lot more to it than just calling .sort() or sorted(). Whether you're a beginner or a seasoned developer, mastering sorting can save you time, make your code cleaner, and even improve performance. Let's dive into the practical ways to sort Python lists like someone who really knows what they're doing.
The Basics: .sort() vs sorted()
First, let's get the fundamentals straight. Python gives you two main ways to sort a list:
list.sort()– This method sorts the list in place, meaning it modifies the original list and returnsNone. Use this when you don't need to keep the original order.sorted(list)– This function returns a new sorted list, leaving the original unchanged. Use this when you need to preserve the original data.
Here's a quick example:
numbers = [3, 1, 4, 1, 5, 9]
numbers.sort()
print(numbers) # Output: [1, 1, 3, 4, 5, 9]
original = [3, 1, 4, 1, 5, 9]
sorted_numbers = sorted(original)
print(original) # Output: [3, 1, 4, 1, 5, 9]
print(sorted_numbers) # Output: [1, 1, 3, 4, 5, 9]
The choice between them is simple: use .sort() when you want to modify the list directly and don't need the original order. Use sorted() when you need to keep the original list intact, or when you're sorting something that isn't a list (like a tuple or dictionary).
Sorting in Reverse Order
Both methods accept a reverse parameter. Set it to True and you'll get descending order:
scores = [88, 92, 79, 95, 84]
scores.sort(reverse=True)
print(scores) # Output: [95, 92, 88, 84, 79]
This is straightforward, but here's a tip: if you're sorting a list of numbers and want the largest first, reverse=True is your friend. For strings, it sorts alphabetically in reverse.
Custom Sorting with the key Parameter
This is where sorting gets really powerful. The key parameter lets you define a function that transforms each element before comparison. You're not changing the elements themselves, just telling Python how to compare them.
Let's say you have a list of strings and you want to sort them by length:
fruits = ["apple", "kiwi", "banana", "cherry", "date"]
fruits.sort(key=len)
print(fruits) # Output: ['kiwi', 'date', 'apple', 'banana', 'cherry']
The key parameter is incredibly versatile. You can use built-in functions, lambda expressions, or even your own custom functions.
Sorting by Multiple Criteria
Sometimes you need to sort by one thing first, then by another. For example, imagine you have a list of dictionaries representing employees, and you want to sort by department first, then by salary within each department:
employees = [
{"name": "Alice", "department": "Engineering", "salary": 85000},
{"name": "Bob", "department": "Marketing", "salary": 72000},
{"name": "Charlie", "department": "Engineering", "salary": 92000},
{"name": "Diana", "department": "Marketing", "salary": 68000}
]
employees.sort(key=lambda e: (e["department"], e["salary"]))
Python sorts tuples lexicographically, so it first compares the department, and if they're equal, it compares the salary. This is a clean way to handle multi-level sorting.
Sorting with Custom Objects
When you have a list of custom objects, you need to tell Python how to compare them. The cleanest approach is to use the key parameter with a lambda or a method reference:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
people = [Person("Alice", 30), Person("Bob", 25), Person("Charlie", 35)]
people.sort(key=lambda p: p.age)
If you're doing this a lot, consider implementing __lt__ (less than) in your class. This makes your objects natively sortable:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __lt__(self, other):
return self.age < other.age
people = [Person("Alice", 30), Person("Bob", 25), Person("Charlie", 35)]
people.sort() # Now works without a key!
Sorting in Descending Order with reverse
We already touched on this, but it's worth emphasizing: reverse=True is your go-to for descending order. It works with both .sort() and sorted():
grades = [88, 92, 79, 95, 84]
top_to_bottom = sorted(grades, reverse=True)
print(top_to_bottom) # Output: [95, 92, 88, 84, 79]
Sorting Strings Case-Insensitively
By default, Python sorts strings using their Unicode code points, which means uppercase letters come before lowercase ones. This can lead to unexpected results:
words = ["apple", "Banana", "cherry", "Date"]
words.sort()
print(words) # Output: ['Banana', 'Date', 'apple', 'cherry']
To sort case-insensitively, use key=str.lower:
words.sort(key=str.lower)
print(words) # Output: ['apple', 'Banana', 'cherry', 'Date']
This is a clean, readable solution that works perfectly.
Sorting by Multiple Keys with Tuples
We saw this earlier, but let's expand on it. When you need to sort by multiple criteria, you can return a tuple from the key function. Python compares the first element, then the second if there's a tie, and so on.
For example, sort a list of tuples by the second element, then by the first:
data = [(1, 'z'), (2, 'a'), (1, 'a'), (3, 'b')]
data.sort(key=lambda x: (x[1], x[0]))
print(data) # Output: [(1, 'a'), (2, 'a'), (1, 'z'), (3, 'b')]
Notice how (1, 'a') comes before (2, 'a') because when the second elements are equal, it falls back to the first element.
Sorting with itemgetter and attrgetter
For cleaner code, especially when sorting by dictionary keys or object attributes, use operator.itemgetter and operator.attrgetter:
from operator import itemgetter, attrgetter
# For dictionaries
students = [
{"name": "Alice", "grade": 85},
{"name": "Bob", "grade": 92},
{"name": "Charlie", "grade": 78}
]
students.sort(key=itemgetter("grade"))
print(students) # Sorted by grade
# For objects
class Student:
def __init__(self, name, grade):
self.name = name
self.grade = grade
students = [Student("Alice", 85), Student("Bob", 92), Student("Charlie", 78)]
students.sort(key=attrgetter("grade"))
These are faster than lambdas in many cases and make your intent clearer.
Sorting with natsort for Natural Order
If you've ever sorted filenames like file1.txt, file10.txt, file2.txt, you know the default sort gives you file1.txt, file10.txt, file2.txt. That's because Python sorts strings lexicographically. For natural sorting (where numbers are treated as numbers), use the natsort library:
from natsort import natsorted
filenames = ["file10.txt", "file2.txt", "file1.txt"]
sorted_files = natsorted(filenames)
print(sorted_files) # Output: ['file1.txt', 'file2.txt', 'file10.txt']
This is a lifesaver when dealing with real-world data like log files, version numbers, or any mixed string-number content.
Sorting with functools.cmp_to_key
If you're coming from Python 2 or have a complex comparison function, you can use functools.cmp_to_key to convert an old-style comparison function to a key function. This is rarely needed in modern Python, but it's good to know:
from functools import cmp_to_key
def compare(a, b):
if a < b:
return -1
elif a > b:
return 1
else:
return 0
numbers = [3, 1, 4, 1, 5, 9]
numbers.sort(key=cmp_to_key(compare))
In practice, you'll almost never need this because the key parameter is more flexible and efficient.
Sorting Lists of Mixed Types
Python 3 doesn't allow sorting lists with mixed types by default. If you try to sort [1, 'two', 3], you'll get a TypeError. But sometimes you have data that's mixed and you need to sort it anyway. One approach is to use a key function that converts everything to a common type:
mixed = [3, 'apple', 1, 'banana', 2]
mixed.sort(key=lambda x: (isinstance(x, str), x))
print(mixed) # Output: [1, 2, 3, 'apple', 'banana']
This sorts numbers first (since isinstance(x, str) is False for numbers, which is less than True), then strings alphabetically. You can adjust the logic to suit your needs.
Stability and Performance
Python's sorting algorithm is Timsort, a hybrid stable sorting algorithm derived from merge sort and insertion sort. It's stable, meaning that if two items have the same key, their original order is preserved. This is useful when you want to sort by multiple criteria in separate passes:
# First sort by name, then by grade (stable sort preserves the first sort)
students = [("Alice", 85), ("Bob", 92), ("Alice", 90), ("Charlie", 85)]
students.sort(key=lambda s: s[0]) # Sort by name
students.sort(key=lambda s: s[1], reverse=True) # Then by grade descending
print(students) # Output: [('Bob', 92), ('Alice', 90), ('Alice', 85), ('Charlie', 85)]
Because Timsort is stable, the second sort preserves the relative order from the first sort when grades are equal.
Sorting with bisect for Insertion
If you have a sorted list and want to insert new items while keeping it sorted, don't sort the whole list again. Use the bisect module:
import bisect
sorted_list = [1, 3, 5, 7, 9]
bisect.insort(sorted_list, 4)
print(sorted_list) # Output: [1, 3, 4, 5, 7, 9]
This is much more efficient than appending and re-sorting, especially for large lists.
Sorting Dictionaries by Keys or Values
Dictionaries aren't sortable themselves, but you can get a sorted list of their items:
scores = {"Alice": 85, "Bob": 92, "Charlie": 78}
# Sort by key
sorted_by_name = sorted(scores.items())
print(sorted_by_name) # Output: [('Alice', 85), ('Bob', 92), ('Charlie', 78)]
# Sort by value
sorted_by_score = sorted(scores.items(), key=lambda item: item[1], reverse=True)
print(sorted_by_score) # Output: [('Bob', 92), ('Alice', 85), ('Charlie', 78)]
If you need a sorted dictionary, use dict(sorted(...)) in Python 3.7+ (where dictionaries maintain insertion order).
Sorting with locale.strxfrm for Language-Aware Sorting
When dealing with text in different languages, the default sort might not respect language-specific rules (like accented characters). Use the locale module for proper linguistic sorting:
import locale
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
words = ['côte', 'cote', 'coté', 'côté']
words.sort(key=locale.strxfrm)
print(words) # Output: ['cote', 'côté', 'cote', 'côte'] (depends on locale)
This is crucial for applications that handle multilingual data.
Sorting with numpy for Performance
If you're working with large numerical datasets, NumPy's sorting is significantly faster than Python's built-in sort:
import numpy as np
arr = np.array([3, 1, 4, 1, 5, 9])
arr.sort()
print(arr) # Output: [1 1 3 4 5 9]
NumPy also supports np.argsort() which returns the indices that would sort the array, useful for indirect sorting.
Common Pitfalls and How to Avoid Them
-
Modifying a list while sorting: Don't do this. It leads to unpredictable behavior. If you need to sort and filter, do it in separate steps.
-
Sorting with
Nonevalues: If your list containsNone, sorting will raise aTypeError. Handle it with a key function:
data = [3, None, 1, None, 2]
data.sort(key=lambda x: (x is None, x))
print(data) # Output: [1, 2, 3, None, None]
This puts None values at the end. You can reverse the logic to put them first.
- Forgetting that
.sort()returnsNone: This is a classic mistake. If you writenew_list = my_list.sort(), you'll getNone. Always usesorted()if you need a new list.
Sorting with heapq for Partial Sorts
If you only need the top N items from a list, don't sort the whole thing. Use heapq.nlargest or heapq.nsmallest:
import heapq
scores = [88, 92, 79, 95, 84, 91, 77]
top_three = heapq.nlargest(3, scores)
print(top_three) # Output: [95, 92, 91]
This is much more efficient than sorting the entire list when N is small relative to the list size.
Sorting with pandas for DataFrames
If you're working with tabular data, pandas offers powerful sorting capabilities:
import pandas as pd
df = pd.DataFrame({
"name": ["Alice", "Bob", "Charlie"],
"score": [85, 92, 78]
})
df_sorted = df.sort_values("score", ascending=False)
print(df_sorted)
Pandas also supports sorting by multiple columns, with different ascending/descending settings for each.
Real-World Example: Sorting Log Entries
Let's say you have log entries like this:
logs = [
"2024-01-15 10:30:45 ERROR: Connection timeout",
"2024-01-15 10:30:42 INFO: User login",
"2024-01-15 10:30:44 WARNING: Disk space low",
"2024-01-15 10:30:43 ERROR: Database connection failed"
]
You want to sort them by timestamp, but the timestamp is at the beginning of each string. You can extract it with a key function:
logs.sort(key=lambda log: log[:19]) # First 19 characters are the timestamp
Or more robustly, parse the timestamp:
from datetime import datetime
logs.sort(key=lambda log: datetime.strptime(log[:19], "%Y-%m-%d %H:%M:%S"))
This gives you chronologically ordered logs.
Performance Tips
- Use
keyinstead ofcmp: Thekeyfunction is called once per element, whilecmpis called many times during comparison. Always preferkey. - Precompute keys: If your key function is expensive, compute the keys once and sort a list of tuples:
# Instead of this:
data.sort(key=expensive_function)
# Do this:
data.sort(key=lambda x: expensive_function(x)) # Still calls expensive_function per element
# Better:
data_with_keys = [(expensive_function(x), x) for x in data]
data_with_keys.sort()
data = [x for _, x in data_with_keys]
This is called the "decorate-sort-undecorate" pattern or Schwartzian transform.
Sorting with functools.partial for Reusable Keys
If you find yourself using the same key function repeatedly, create a reusable one with functools.partial:
from functools import partial
def sort_by_field(field, item):
return item[field]
sort_by_name = partial(sort_by_field, "name")
sort_by_age = partial(sort_by_field, "age")
people = [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]
people.sort(key=sort_by_name)
This keeps your code DRY and makes sorting logic reusable.
Sorting with random.shuffle for Randomization
Sometimes you don't want to sort at all – you want to randomize. random.shuffle does this in place:
import random
cards = ["Ace", "King", "Queen", "Jack"]
random.shuffle(cards)
print(cards) # Random order each time
This is useful for games, sampling, or any scenario where you need to mix things up.
Final Thoughts
Sorting in Python is deceptively simple. The basics are easy, but the real power comes from the key parameter, stability, and knowing when to use specialized tools like natsort, heapq, or bisect. At PythonSkillset, we believe that understanding these nuances separates good code from great code.
Next time you need to sort a list, think about what you're really comparing. Is it a simple value? A computed property? A combination of fields? The key parameter is your Swiss Army knife – use it wisely, and your sorting will be both efficient and readable.
Advertisement
Comments
Questions, corrections, and tips stay visible for everyone reading this page.
Join the discussion
No comments yet
Be the first to leave a note — it helps the next reader.