python sorted函数详细剖析

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() 优雅解决。

相关推荐
qq_355939491 小时前
标签打印发虚、断线、糊成一团?先分清是哪种“不清“
1024程序员节
打工仔折腾 AI1 小时前
AI Agent 工程化实战(二):5 分钟跑通 Deep Agent 并逐层拆解底层链路
java·数据库·人工智能·后端·python·langchain·ai agent 实战
外收内放1 小时前
FastAPI猜字谜实战(4):让AI主持人开口
开发语言·python·学习·fastapi
云和数据.ChenGuang1 小时前
Python map() 函数详解:从语法、原理到实战用法
开发语言·人工智能·python·langchain·1024程序员节
一马平川的大草原1 小时前
企业语义本体(Ontology)构建方法论及智能体应用的保障机制
人工智能·智能体·语义本体
秋名RG1 小时前
(二)存储、CPU 与指令执行——程序为什么快,又为什么慢
java·开发语言
Highcharts.js1 小时前
Highcharts甘特图开发带导航功能的项目管理图示例
开发语言·前端·javascript·甘特图·highcharts·项目管理图
kyle~1 小时前
C++ 原子操作、内存模型与无锁并发编程
linux·开发语言·c++·操作系统