1. 引言
在 Python 中,排序是最常见的操作之一。sorted() 是内置函数,它不会修改原对象,而是返回一个新的已排序列表 。相比列表自带的 list.sort()(原地排序、返回 None),sorted() 可以作用于任何可迭代对象,例如列表、元组、字典、集合、生成器,甚至字符串。
本文从基础语法出发,逐步覆盖 key、reverse、多字段排序、自定义对象排序、稳定性、性能等细节,并给出大量可直接运行的案例。
2. 函数签名与基本语法
python
sorted(iterable, /, *, key=None, reverse=False)
参数说明:
| 参数 | 说明 |
|---|---|
iterable |
必填,任意可迭代对象 |
key |
可选,接收一个函数,用于从每个元素中提取比较键 |
reverse |
可选,布尔值,True 表示降序,默认 False |
返回值:一个新的 list。
python
nums = [3, 1, 4, 1, 5, 9, 2, 6]
result = sorted(nums)
print(result) # [1, 1, 2, 3, 4, 5, 6, 9]
print(nums) # [3, 1, 4, 1, 5, 9, 2, 6] 原列表未被修改
3. 基础用法案例
3.1 对列表排序
python
numbers = [42, 7, 19, 3, 88]
print(sorted(numbers)) # [3, 7, 19, 42, 88]
print(sorted(numbers, reverse=True)) # [88, 42, 19, 7, 3]
3.2 对字符串排序
字符串本身是可迭代对象,sorted() 会把它拆成字符列表:
python
print(sorted("python")) # ['h', 'n', 'o', 'p', 't', 'y']
3.3 对元组、集合排序
python
print(sorted((5, 2, 9, 1))) # [1, 2, 5, 9]
print(sorted({3, 1, 2})) # [1, 2, 3]
3.4 对字典排序
对字典直接排序,得到的是键的排序结果:
python
scores = {"alice": 90, "bob": 75, "carol": 88}
print(sorted(scores)) # ['alice', 'bob', 'carol']
print(sorted(scores.keys())) # 同上
print(sorted(scores.values())) # [75, 88, 90]
print(sorted(scores.items())) # [('alice', 90), ('bob', 75), ('carol', 88)]
3.5 对生成器排序
sorted() 会先把可迭代对象消费成列表,因此生成器也可以直接排序:
python
gen = (x * x for x in range(6))
print(sorted(gen, reverse=True)) # [25, 16, 9, 4, 1, 0]
4. key 参数详解
key 是 sorted() 最强大的部分。它接收一个函数,sorted() 会用该函数对每个元素求值,再按返回值排序,但最终返回的仍是原始元素。
4.1 按绝对值排序
python
nums = [-5, 3, -1, 4, -2]
print(sorted(nums, key=abs)) # [-1, -2, 3, 4, -5]
4.2 按字符串长度排序
python
words = ["banana", "apple", "kiwi", "strawberry"]
print(sorted(words, key=len)) # ['kiwi', 'apple', 'banana', 'strawberry']
4.3 忽略大小写排序
python
names = ["Bob", "alice", "Charlie", "david"]
print(sorted(names)) # ['Bob', 'Charlie', 'alice', 'david']
print(sorted(names, key=str.lower)) # ['alice', 'Bob', 'Charlie', 'david']
4.4 使用 lambda 自定义键
python
students = [
{"name": "Tom", "score": 88},
{"name": "Jerry", "score": 95},
{"name": "Spike", "score": 72},
]
by_score = sorted(students, key=lambda s: s["score"], reverse=True)
for s in by_score:
print(s["name"], s["score"])
# Jerry 95
# Tom 88
# Spike 72
4.5 使用 operator 模块提升可读性与性能
python
from operator import itemgetter, attrgetter
students = [
{"name": "Tom", "score": 88},
{"name": "Jerry", "score": 95},
{"name": "Spike", "score": 72},
]
print(sorted(students, key=itemgetter("score")))
itemgetter 比 lambda 略快,且语义更清晰,适合字典、元组等下标访问场景。
5. 多字段排序案例
5.1 元组排序(先按第一列,再按第二列)
python
data = [(2, "b"), (1, "c"), (2, "a"), (1, "a")]
print(sorted(data))
# [(1, 'a'), (1, 'c'), (2, 'a'), (2, 'b')]
5.2 按多个键排序(itemgetter 多参数)
python
from operator import itemgetter
records = [
("Tom", 20, 88),
("Jerry", 20, 95),
("Spike", 19, 72),
("Tyke", 20, 88),
]
# 先按年龄升序,再按分数降序
result = sorted(records, key=itemgetter(1, 2))
print(result)
# [('Spike', 19, 72), ('Tom', 20, 88), ('Tyke', 20, 88), ('Jerry', 20, 95)]
5.3 混合升降序:先升后降
sorted() 的 reverse 是全局的,无法直接对某个字段单独降序。常用技巧是对需要降序的数值字段取负:
python
records = [
("Tom", 20, 88),
("Jerry", 20, 95),
("Spike", 19, 72),
]
# 年龄升序,分数降序
result = sorted(records, key=lambda r: (r[1], -r[2]))
print(result)
# [('Spike', 19, 72), ('Tom', 20, 88), ('Jerry', 20, 95)]
如果字段是字符串无法取负,可以分两次排序(利用稳定性,见第 7 节):
python
# 先按次要字段升序,再按主要字段降序
tmp = sorted(records, key=lambda r: r[2]) # 分数升序
result = sorted(tmp, key=lambda r: r[1], reverse=True) # 年龄降序
print(result)
6. 自定义对象排序
6.1 使用 attrgetter
python
from operator import attrgetter
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def __repr__(self):
return f"Student({self.name!r}, {self.score})"
students = [Student("Tom", 88), Student("Jerry", 95), Student("Spike", 72)]
print(sorted(students, key=attrgetter("score"), reverse=True))
# [Student('Jerry', 95), Student('Tom', 88), Student('Spike', 72)]
6.2 使用 lt 让对象可比较
python
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def __lt__(self, other):
return self.score < other.score
def __repr__(self):
return f"Student({self.name!r}, {self.score})"
students = [Student("Tom", 88), Student("Jerry", 95), Student("Spike", 72)]
print(sorted(students))
# [Student('Spike', 72), Student('Tom', 88), Student('Jerry', 95)]
6.3 使用 dataclass 与 key
python
from dataclasses import dataclass
@dataclass
class Product:
name: str
price: float
stock: int
products = [
Product("pen", 2.5, 100),
Product("book", 15.0, 20),
Product("bag", 15.0, 5),
]
# 价格升序,价格相同则库存升序
print(sorted(products, key=lambda p: (p.price, p.stock)))
# [Product(name='pen', price=2.5, stock=100), Product(name='bag', price=15.0, stock=5), Product(name='book', price=15.0, stock=20)]
7. 排序稳定性
Python 的排序是稳定排序:相等元素的相对顺序会被保留。这一特性让「多轮排序」成为实现复杂排序的常用手段。
python
data = [("b", 2), ("a", 2), ("c", 1)]
# 先按第二列升序
step1 = sorted(data, key=lambda x: x[1])
print(step1) # [('c', 1), ('b', 2), ('a', 2)]
# 再按第一列升序,第二列相同的元素保持上一步顺序
step2 = sorted(step1, key=lambda x: x[0])
print(step2) # [('a', 2), ('b', 2), ('c', 1)]
8. sorted 与 list.sort 的区别
| 对比项 | sorted() |
list.sort() |
|---|---|---|
| 作用对象 | 任意可迭代对象 | 仅列表 |
| 是否修改原对象 | 否 | 是 |
| 返回值 | 新列表 | None |
| 内存占用 | 需要额外空间 | 原地排序,更省内存 |
python
nums = [3, 1, 2]
new_nums = sorted(nums)
print(nums, new_nums) # [3, 1, 2] [1, 2, 3]
ret = nums.sort()
print(nums, ret) # [1, 2, 3] None
9. 常见坑与注意事项
9.1 混合类型比较会报错
Python 3 中不同类型不能直接比较:
python
try:
sorted([1, "2", 3])
except TypeError as e:
print("TypeError:", e)
# TypeError: '<' not supported between instances of 'str' and 'int'
解决方式是统一转换类型,或用 key 提取可比较的键:
python
mixed = [1, "2", 3, "10"]
print(sorted(mixed, key=int)) # [1, '2', 3, '10']
9.2 key 函数会被调用一次
sorted() 会对每个元素调用一次 key,并缓存结果(Decorate-Sort-Undecorate 模式),因此不必担心 key 被重复执行。
9.3 不要用 key 修改原数据
key 函数应保持纯粹,只做取值,不要在其中修改元素,否则结果不可预期。
9.4 排序 None 值
None 无法与其他类型比较,需要显式处理:
python
data = [3, None, 1, None, 2]
result = sorted(data, key=lambda x: (x is None, x if x is not None else 0))
print(result) # [1, 2, 3, None, None]
10. 性能与实践建议
- 时间复杂度为
O(n log n),底层使用 Timsort,对部分有序数据表现更好。 - 优先使用
operator.itemgetter/attrgetter代替lambda,可读性和速度都更优。 - 只需要前 K 个结果时,用
heapq.nlargest/heapq.nsmallest比全量排序更高效:
python
import heapq
nums = [5, 1, 9, 3, 7, 2, 8]
print(heapq.nlargest(3, nums)) # [9, 8, 7]
print(heapq.nsmallest(3, nums)) # [1, 2, 3]
- 大数据量且只需原地排序时,用
list.sort()节省内存。
11. 综合实战案例
11.1 按成绩排名并处理并列
python
students = [
{"name": "Tom", "score": 88},
{"name": "Jerry", "score": 95},
{"name": "Spike", "score": 88},
{"name": "Tyke", "score": 72},
]
# 分数降序,同分按姓名升序
ranked = sorted(students, key=lambda s: (-s["score"], s["name"]))
for i, s in enumerate(ranked, 1):
print(i, s["name"], s["score"])
# 1 Jerry 95
# 2 Spike 88
# 3 Tom 88
# 4 Tyke 72
11.2 按日期字符串排序
python
from datetime import datetime
dates = ["2024-03-01", "2023-12-25", "2024-01-15"]
result = sorted(dates, key=lambda d: datetime.strptime(d, "%Y-%m-%d"))
print(result) # ['2023-12-25', '2024-01-15', '2024-03-01']
11.3 按文件扩展名分组排序
python
files = ["a.txt", "b.py", "c.md", "d.txt", "e.py"]
result = sorted(files, key=lambda f: (f.split(".")[-1], f))
print(result) # ['c.md', 'b.py', 'e.py', 'a.txt', 'd.txt']
11.4 对字典按值排序并转回字典
python
scores = {"alice": 90, "bob": 75, "carol": 88}
sorted_dict = dict(sorted(scores.items(), key=lambda kv: kv[1], reverse=True))
print(sorted_dict) # {'alice': 90, 'carol': 88, 'bob': 75}
12. 总结
sorted()返回新列表,不修改原对象,适用于任意可迭代对象。key决定排序依据,reverse控制升降序。- 多字段排序可用元组键、
itemgetter,混合升降序可用取负或多次稳定排序。 - 排序是稳定的,善用稳定性可以简化复杂排序逻辑。
- 注意类型一致性、
None值处理,并在性能敏感场景选择合适的工具。
掌握以上案例后,绝大多数排序需求都可以用一行 sorted() 优雅解决。