Of course! Understanding Python's sort order is fundamental to working with data. The behavior depends on the type of data you're sorting and the specific sorting method you use.
Let's break it down into the two primary ways to sort in Python:
- The
list.sort()method (for lists) - The built-in
sorted()function (for any iterable)
For the most part, they work identically in terms of sorting logic, but sorted() returns a new sorted list, while list.sort() modifies the list in-place.
Sorting Basic Data Types (Numbers and Strings)
For simple, comparable types, Python's default behavior is intuitive.
Numbers (Integers and Floats)
Numbers are sorted in ascending numerical order by default.
numbers = [3, 1, 4, 1, 5, 9, 2, 6] numbers.sort() print(numbers) # Output: [1, 1, 2, 3, 4, 5, 6, 9] # Using sorted() numbers_desc = sorted(numbers, reverse=True) print(numbers_desc) # Output: [9, 6, 5, 4, 3, 2, 1, 1]
Strings
Strings are sorted lexicographically, which is similar to alphabetical order but based on the ASCII/Unicode value of each character.
- Uppercase letters come before lowercase letters.
- Numbers come before letters.
words = ["banana", "Apple", "cherry", "date", "apricot"] words.sort() print(words) # Output: ['Apple', 'apricot', 'banana', 'cherry', 'date'] # Notice 'Apple' (with 'A') comes before 'apricot' (with 'a'). # This is because the ASCII value of 'A' (65) is less than 'a' (97).
Sorting Custom Objects (The Most Important Case)
This is where you'll spend most of your time. By default, Python doesn't know how to compare two instances of your own class.
Let's say we have a Product class:
class Product:
def __init__(self, name, price, stock):
self.name = name
self.price = price
self.stock = stock
def __repr__(self): # A nice string representation for printing
return f"Product(name='{self.name}', price={self.price}, stock={self.stock})"
If you try to sort a list of Product objects, you'll get a TypeError.
products = [
Product("Laptop", 1200, 10),
Product("Mouse", 25, 50),
Product("Keyboard", 75, 30)
]
# This will raise a TypeError!
# products.sort()
To fix this, you need to tell Python how to compare the objects. You do this with the key argument.
The key Argument
The key argument accepts a function that is called on every element in the list before making comparisons. The sorting is then performed on the return values of this function.
Example: Sorting by Price
We want to sort our products by their price attribute.
# Using a lambda function for the key # The lambda takes one argument 'p' (a Product object) and returns p.price products.sort(key=lambda p: p.price) print(products) # Output: # [Product(name='Mouse', price=25, stock=50), # Product(name='Keyboard', price=75, stock=30), # Product(name='Laptop', price=1200, stock=10)]
Example: Sorting by Name (Alphabetically)
products.sort(key=lambda p: p.name) print(products) # Output: # [Product(name='Keyboard', price=75, stock=30), # Product(name='Laptop', price=1200, stock=10), # Product(name='Mouse', price=25, stock=50)]
Example: Sorting by Stock (Descending)
To sort in descending order, you can either set reverse=True or return a negative value from the key if your data is numeric. Using reverse=True is more readable.
# Method 1: Using reverse=True (Recommended) products.sort(key=lambda p: p.stock, reverse=True) # Method 2: Negating the key value (works for numbers) # products.sort(key=lambda p: -p.stock) print(products) # Output: # [Product(name='Mouse', price=25, stock=50), # Product(name='Keyboard', price=75, stock=30), # Product(name='Laptop', price=1200, stock=10)]
Sorting by Multiple Keys
What if you want to sort primarily by one attribute and secondarily by another? You can do this by returning a tuple from the key function. Python sorts tuples element by element.
Example: Sort by stock (descending), then by name (ascending)
If two products have the same stock level, we want them to be sorted alphabetically by name.
products = [
Product("Laptop", 1200, 10),
Product("Mouse", 25, 50),
Product("Keyboard", 75, 30),
Product("Webcam", 100, 50) # Same stock as Mouse
]
# The key returns a tuple: (primary_sort_key, secondary_sort_key)
# We use -p.stock for descending order on the first element
products.sort(key=lambda p: (-p.stock, p.name))
print(products)
# Output:
# [Product(name='Mouse', price=25, stock=50),
# Product(name='Webcam', price=100, stock=50), # Sorted by name after stock
# Product(name='Keyboard', price=75, stock=30),
# Product(name='Laptop', price=1200, stock=10)]
The __lt__ Magic Method (Object-Oriented Sorting)
Instead of using a key function, you can define the less-than operator (<) directly in your class. This makes your objects inherently comparable.
class ProductWithCompare:
def __init__(self, name, price, stock):
self.name = name
self.price = price
self.stock = stock
def __repr__(self):
return f"ProductWithCompare(name='{self.name}', price={self.price}, stock={self.stock})"
def __lt__(self, other):
"""Define the less-than operator for sorting by price."""
return self.price < other.price
# Now you can sort without a key!
products2 = [
ProductWithCompare("Laptop", 1200, 10),
ProductWithCompare("Mouse", 25, 50),
ProductWithCompare("Keyboard", 75, 30)
]
products2.sort()
print(products2)
# Output:
# [ProductWithCompare(name='Mouse', price=25, stock=50),
# ProductWithCompare(name='Keyboard', price=75, stock=30),
# ProductWithCompare(name='Laptop', price=1200, stock=10)]
When to use which?
keyfunction: More flexible. You can easily change the sorting criteria without modifying the class. You can also sort by attributes that don't have a natural<relationship (e.g., length of a string). This is the more common and Pythonic approach.__lt__method: Good for defining a single, canonical "natural sort order" for your class. It makes the object's behavior more intuitive in other contexts (like using themin()function).
Stability in Sorting
Python's sorting algorithm is stable. This means that if two items have equal keys, their original relative order will be preserved in the sorted output.
This is incredibly useful for multi-level sorting. Instead of creating a complex tuple key, you can sort multiple times in reverse order of importance.
Example: Sort by stock (descending), then by name (ascending) using stability
- First, sort by the secondary key (
name). - Then, sort by the primary key (
stock). The stability ensures that items with equalstockremain in thename-sorted order.
products = [
Product("Laptop", 1200, 10),
Product("Mouse", 25, 50),
Product("Keyboard", 75, 30),
Product("Webcam", 100, 50)
]
# Step 1: Sort by secondary key (name)
products.sort(key=lambda p: p.name)
# Step 2: Sort by primary key (stock)
# The stability preserves the name-sorted order for items with equal stock
products.sort(key=lambda p: p.stock, reverse=True)
print(products)
# Output:
# [Product(name='Mouse', price=25, stock=50),
# Product(name='Webcam', price=100, stock=50),
# Product(name='Keyboard', price=75, stock=30),
# Product(name='Laptop', price=1200, stock=10)]
This gives the exact same result as the tuple key method but can sometimes be easier to read and manage.
Summary Table
| Method | What it Does | When to Use | Example |
|---|---|---|---|
list.sort() |
Sorts a list in-place. | When you want to modify the original list and don't need to keep the old order. | my_list.sort() |
sorted() |
Returns a new sorted list. | When you want to keep the original list unchanged or sort an iterable that isn't a list. | new_list = sorted(my_list) |
key=func |
Sorts based on the value returned by func for each item. |
This is the most common and flexible method. Use it for custom objects and complex sorting logic. | my_list.sort(key=lambda x: x.attribute) |
reverse=True |
Sorts in descending order. | When you need the opposite of the default ascending order. | my_list.sort(reverse=True) |
__lt__ |
Defines the < operator for a class. |
When you want to define a single, "natural" sort order for your objects. | class MyClass: def __lt__(self, other): ... |
| Stability | Preserves original order of equal items. | Crucial for multi-level sorting done in multiple steps. | list1.sort(key=key1); list1.sort(key=key2, reverse=True) |
