跳到主要内容

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),并为非空值提供同类型默认值。

练习​

  1. 使用 sorted() 将数字列表按绝对值升序排列。
  2. 将学生记录按分数降序排列,分数相同时按姓名升序排列。
  3. 将一组单词按忽略大小写后的字典序排列。
  4. 比较 sorted() 和 list.sort() 对原列表与返回值的影响。
  5. 使用 heapq.nsmallest() 找出工资最低的三名员工,并说明它与完整排序的区别。

小结​

  • sorted() 接收任意可迭代对象,返回新的列表,不修改输入。
  • key 提供每个元素的排序依据;reverse=True 表示降序。
  • Python 排序稳定,相同键的元素保留原始相对顺序;多条件排序常用元组键。
  • list.sort() 只适用于列表并原地修改,返回 None;选择时要明确是否需要保留原数据。
  • 排序会读取全部输入,时间复杂度通常为 O(n log n);超大数据或只需少量极值时应选择更合适的方案。