sorted
sorted() 将一个可迭代对象中的元素按顺序排列,并返回一个新的列表。它不会修改原数据,适用于列表、元组、字典、集合、字符串以及生成器等各种可迭代对象。
本文使用 Python 3.10 及以上版本。读完后,你应该能够写出单条件和多条件排序,理解 key、reverse 与稳定排序,并知道何时改用原地排序。
基本语法
sorted(iterable, *, key=None, reverse=False)
iterable:要排序的可迭代对象;key:从每个元素提取比较依据的函数,默认为None,直接比较元素本身;reverse:为True时按降序排列,默认为False。
numbers = [4, 1, 3, 2]
result = sorted(numbers)
print(result) # [1, 2, 3, 4]
print(numbers) # [4, 1, 3, 2],原列表没有变化
因为 sorted() 接收函数参数 key,它也是高阶函数。函数名或 lambda 都可以作为键函数传入。
key:指定排序依据
按字符串长度排序
words = ["Python", "Go", "JavaScript", "C"]
by_length = sorted(words, key=len)
print(by_length) # ['C', 'Go', 'Python', 'JavaScript']
排序时不会改变元素本身,而是比较 key(element) 的结果。键函数应接收一个参数,并返回可比较的值。
按字典字段排序
students = [
{"name": "Alice", "score": 88},
{"name": "Bob", "score": 95},
{"name": "Cindy", "score": 91},
]
by_score = sorted(students, key=lambda student: student["score"])
print([student["name"] for student in by_score]) # ['Alice', 'Cindy', 'Bob']
当字段一定存在时,itemgetter() 可以让意图更明确:
from operator import itemgetter
by_score = sorted(students, key=itemgetter("score"), reverse=True)
print([student["name"] for student in by_score]) # ['Bob', 'Cindy', 'Alice']
字段可能缺失时,使用 dict.get() 并提供合适的默认值:
records = [{"name": "A", "score": 80}, {"name": "B"}]
by_score = sorted(records, key=lambda record: record.get("score", 0))
print(by_score[0]["name"]) # B
升序、降序与 reverse
numbers = [3, 1, 4, 2]
print(sorted(numbers)) # [1, 2, 3, 4]
print(sorted(numbers, reverse=True)) # [4, 3, 2, 1]
print(sorted(numbers, key=abs)) # [1, 2, 3, 4]
print(sorted(numbers, key=abs, reverse=True)) # [4, 3, 2, 1]
reverse=True 会反转最终的排序方向,但不会破坏稳定性:键值相同的元素仍保持输入中的相对顺序。它不等同于先升序再调用 list.reverse(),后者会把整个列表(包括相等键的元素)一起倒置。
稳定排序
Python 的排序是稳定的:两个元素的排序键相同,它们在结果中的相对顺序与输入保持一致。
tasks = [
{"title": "写报告", "priority": 1},
{"title": "修复测试", "priority": 1},
{"title": "更新文档", "priority": 2},
]
ordered = sorted(tasks, key=lambda task: task["priority"])
print([task["title"] for task in ordered]) # ['写报告', '修复测试', '更新文档']
稳定性可以用来分两次排序:先按次要条件排序,再按主要条件排序。第二次排序不会打乱第一次排序中相同主键的顺序:
students = [
{"name": "Bob", "class": "A", "score": 90},
{"name": "Alice", "class": "A", "score": 90},
{"name": "Cindy", "class": "B", "score": 95},
]
by_name = sorted(students, key=lambda student: student["name"])
by_score_then_name = sorted(by_name, key=lambda student: student["score"], reverse=True)
print([student["name"] for student in by_score_then_name]) # ['Cindy', 'Alice', 'Bob']
更常见、更高效的写法是一次排序返回元组键:
by_score_then_name = sorted(
students,
key=lambda student: (-student["score"], student["name"]),
)
这里分数使用负号实现降序,姓名仍为升序。数值字段才能直接取负;字符串等其他类型应使用多次稳定排序,或封装明确的比较键。
多条件排序
元组按从左到右的顺序比较,因此可以组合多个条件:
employees = [
{"name": "Alice", "department": "研发", "salary": 18000},
{"name": "Bob", "department": "研发", "salary": 16000},
{"name": "Cindy", "department": "产品", "salary": 18000},
]
ordered = sorted(
employees,
key=lambda employee: (employee["department"], -employee["salary"]),
)
print([(item["department"], item["name"]) for item in ordered])
# [('产品', 'Cindy'), ('研发', 'Alice'), ('研发', 'Bob')]
不要让同一个键元组混入无法互相比较的类型,例如同时出现整数和字符串;必要时先统一数据类型。
sorted() 与 list.sort()
两者都使用 Python 的稳定排序算法,但返回值和用途不同:
| 特性 | sorted(iterable) | list.sort() |
|---|---|---|
| 输入 | 任意可迭代对象 | 只能是列表 |
| 是否修改原数据 | 不修改,返回新列表 | 原地修改,返回 None |
| 适合场景 | 需要保留原顺序、处理元组或生成器 | 不再需要原顺序、希望减少额外列表 |
| 典型错误 | 忘记接收返回值 | 写成 result = values.sort() 后得到 None |
values = [3, 1, 2]
values.sort()
print(values) # [1, 2, 3]
values = [3, 1, 2]
result = values.sort()
print(result) # None
list.sort() 通常只需要排序期间的辅助内存;sorted() 必须创建并返回一个新列表。无论使用哪一个,都不应依赖排序函数修改元素内部状态。
处理不同类型的可迭代对象
print(sorted((3, 1, 2))) # [1, 2, 3]
print(sorted({3, 1, 2})) # [1, 2, 3]
print(sorted("python")) # ['h', 'n', 'o', 'p', 't', 'y']
print(sorted({"b": 2, "a": 1})) # ['a', 'b'],字典默认迭代键
生成器也能排序,但排序必须先读取全部元素,因此结果不再是惰性的:
numbers = (number * number for number in range(4))
print(sorted(numbers, reverse=True)) # [9, 4, 1, 0]
如果数据来自无限生成器或非常大的数据流,不能直接调用 sorted();应考虑限制数据量、分块排序或使用专门的外部排序方案。
忽略大小写和自定义转换
默认字符串排序按 Unicode 码点比较,大写字母通常会排在小写字母前。使用 str.casefold() 可以进行更适合人类阅读的大小写无关排序:
names = ["bob", "Alice", "cindy", "ALAN"]
print(sorted(names, key=str.casefold)) # ['ALAN', 'Alice', 'bob', 'cindy']
日期、版本号等数据应先转换为真正可比较的类型,而不是依赖字符串的偶然格式:
from datetime import date
events = [
{"name": "发布", "day": date(2025, 3, 1)},
{"name": "测试", "day": date(2025, 2, 20)},
]
print([event["name"] for event in sorted(events, key=lambda event: event["day"])])
# ['测试', '发布']
键函数的调用与性能
排序需要比较很多次,但 Python 会为每个元素计算一次 key 值,再使用这些值排序。因此,把昂贵的转换写进 key 通常比手动循环反复计算更合适:
from operator import attrgetter
class User:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
users = [User("A", 30), User("B", 20)]
users_by_age = sorted(users, key=attrgetter("age"))
print([user.name for user in users_by_age]) # ['B', 'A']
排序的时间复杂度通常为 O(n log n),并且至少需要把全部输入放入内存。数据量很大时,可以先筛选、限制数量,或使用 heapq.nsmallest() / heapq.nlargest() 获取少量极值。
常见错误与排查方法
忘记使用返回值
sorted() 不修改原列表:
values = [3, 1, 2]
sorted(values)
print(values) # [3, 1, 2]
应写成 ordered = sorted(values),或直接使用表达式结果。
比较不可排序的混合类型
# sorted([1, "2"]) # TypeError:int 与 str 不能直接比较
先统一类型,例如 sorted(values, key=str),但要确认这种转换符合业务含义。
key 写成调用结果
words = ["a", "bbb"]
# sorted(words, key=len()) # TypeError:len() 缺少参数
print(sorted(words, key=len))
key 需要传入“以后针对每个元素调用的函数”,因此写函数名或 lambda,不要在调用处提前执行。
直接比较含 None 的字段
records = [{"score": 80}, {"score": None}]
# sorted(records, key=lambda record: record["score"]) # TypeError
ordered = sorted(records, key=lambda record: record["score"] is None)
上例把 None 统一放到最后;更复杂的规则可以返回 (is_none, value),并为非空值提供同类型默认值。
练习
- 使用
sorted()将数字列表按绝对值升序排列。 - 将学生记录按分数降序排列,分数相同时按姓名升序排列。
- 将一组单词按忽略大小写后的字典序排列。
- 比较
sorted()和list.sort()对原列表与返回值的影响。 - 使用
heapq.nsmallest()找出工资最低的三名员工,并说明它与完整排序的区别。
小结
sorted()接收任意可迭代对象,返回新的列表,不修改输入。key提供每个元素的排序依据;reverse=True表示降序。- Python 排序稳定,相同键的元素保留原始相对顺序;多条件排序常用元组键。
list.sort()只适用于列表并原地修改,返回None;选择时要明确是否需要保留原数据。- 排序会读取全部输入,时间复杂度通常为
O(n log n);超大数据或只需少量极值时应选择更合适的方案。