Python 排序指南:sorted() 函数从入门到高级用法全覆盖

0. 写在前面

排序几乎是所有开发者日常都会用到的操作——无论是整理数据报表、筛选Top N结果,还是美化列表输出,高效灵活的排序工具都能帮上大忙。Python 没有让我们自己手写冒泡、快速、归并这些算法,而是直接内置了经过极致优化的稳定排序算法 Timsort(Python 2.3+ 默认实现),并封装成了我们今天要聊的主角:sorted()

接下来我们会从最基础的整数/字符串排序,一步步进阶到复杂对象、多级排序、性能优化这些场景,全程用可复现的代码示例演示。


1. 基础入门:直接对序列排序

sorted() 的核心优势在于通用性强——它可以接受字符串、列表、元组、生成器等所有可迭代对象,并且永远返回一个新的排好序的列表(原输入不会被修改,这点非常安全!)。

1.1 整数/浮点数排序

默认按数值从小到大(升序)排列:

>>> sorted([36, 5, -12, 9, -21])  # 列表
[-21, -12, 5, 9, 36]

>>> sorted((2.5, 0.3, -7.2, 9.9))  # 元组
[-7.2, 0.3, 2.5, 9.9]

>>> sorted(x for x in range(10, 0, -2))  # 生成器
[2, 4, 6, 8, 10]

2. 进阶核心:用 key 自定义排序规则

如果说“默认排序”是入门级操作,那key 参数就是 sorted() 的灵魂——它允许我们传入一个函数,这个函数会作用于输入的每一个元素,生成“排序依据值”,最后 sorted() 只会比较这些依据值的大小。

2.1 简单的数值变形排序

比如开头的例子,按绝对值升序排:

>>> sorted([36, 5, -12, 9, -21], key=abs)  # key=abs 表示用绝对值做依据
[5, 9, -12, -21, 36]

2.2 字符串排序避坑与优化

坑点:默认按 ASCII/Unicode 码值排

英文大小写字母的 Unicode 码值是大写(A-Z: 65-90)小于小写(a-z: 97-122)的,所以直接排序会把大写开头的单词全放前面:

>>> sorted(['bob', 'about', 'Zoo', 'Credit'])
['Credit', 'Zoo', 'about', 'bob']

优化:忽略大小写排序

keystr.lower()str.upper() 就行(选 lower 更通用,比如处理某些非英文字符的大小写):

>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']

3. 常用附加:reverse=True 降序排序

想从大到小排?直接加 reverse=True 参数,简单直接:

>>> sorted([36, 5, -12, 9, -21], reverse=True)
[36, 9, 5, -12, -21]

>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']

4. 高级应用:复杂对象排序

开发中我们很少只排纯整数/纯字符串,更多是排元组列表、字典列表、自定义类实例列表这类复杂数据,这时候 key 的优势就更明显了。

先准备一个学生元组列表当测试数据:

students = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88), ('Alice', 75)]

4.1 元组列表排序

方案1:自定义普通函数

比如按名字升序(忽略大小写)排:

def get_student_lower_name(student):
    return student[0].lower()  # 取元组的第0个元素(名字)转小写

sorted_students_name = sorted(students, key=get_student_lower_name)
print(sorted_students_name)
# 输出: [('Adam', 92), ('Alice', 75), ('Bart', 66), ('Bob', 75), ('Lisa', 88)]

方案2:用 lambda 表达式简化

如果 key 函数逻辑很简单,一行就能写完,没必要单独定义,直接用 lambda 匿名函数更清爽:

# 按名字升序(忽略大小写)
sorted_students_name = sorted(students, key=lambda s: s[0].lower())

# 按成绩升序
sorted_students_score = sorted(students, key=lambda s: s[1])
print(sorted_students_score)
# 输出: [('Bart', 66), ('Bob', 75), ('Alice', 75), ('Lisa', 88), ('Adam', 92)]

方案3:用 operator 模块提高性能

operator 模块提供了 itemgetter()attrgetter() 这类函数,专门用来快速获取对象的索引值、属性值,比 lambda 表达式快 10%-20%(大型数据集更明显):

from operator import itemgetter

# 按名字升序(默认区分大小写,要忽略的话可以嵌套 lower)
sorted_students_name = sorted(students, key=itemgetter(0))

# 按成绩升序
sorted_students_score = sorted(students, key=itemgetter(1))

4.2 多级排序

如果遇到“第一优先级按成绩降序,第二优先级按名字升序”这种需求,怎么做?

其实很简单——利用 Python 元组的逐位比较特性(先比第0位,相等再比第1位,以此类推),给 key 返回一个包含多级依据的元组就行:

# 第一级:成绩降序 → 可以用 -成绩(仅适用于数值型)或者后面加 reverse=True
# 第二级:名字升序 → 忽略大小写
sorted_students_multi = sorted(students, key=lambda s: (-s[1], s[0].lower()))
print(sorted_students_multi)
# 输出: [('Adam', 92), ('Lisa', 88), ('Alice', 75), ('Bob', 75), ('Bart', 66)]

itemgetter() 也能实现多级排序(但只能处理相同方向的优先级,混合升序降序还是要结合 lambda 或分开 reverse):

# 先按成绩升序,再按名字升序
sorted_students_multi_itemgetter = sorted(students, key=itemgetter(1, 0))

5. 性能与稳定性小贴士

虽然 Python 的 Timsort 已经非常快了,但在处理百万级以上数据时,还是要注意几个点:

5.1 尽量用 operator 模块的函数

前面提到过,itemgetter()/attrgetter() 比 lambda 快,因为它们是用 C 语言实现的,解释器开销更小。

5.2 尽量避免重复计算 key

如果同一个数据集需要多次排序,不要每次都让 sorted() 重新计算每个元素的 key——可以先预计算 key,保存成元组列表,排序后再提取原数据(这叫“装饰-排序-去装饰”模式,其实 Timsort 内部已经优化过,但手动处理超大列表可能更明显)。

5.3 利用 Timsort 的稳定性

Python 的排序是稳定排序——如果两个元素的 key 完全相等,它们在排序后的相对位置和原列表一致。比如前面的测试数据里,Alice 和 Bob 都是75分,原列表中 Bob 在前,但排序时第二优先级按名字,所以 Alice 跑到前面了;如果去掉第二优先级,原列表的相对位置就会保留:

sorted_students_stable = sorted(students, key=itemgetter(1))
print(sorted_students_stable)
# 输出: [('Bart', 66), ('Bob', 75), ('Alice', 75), ('Lisa', 88), ('Adam', 92)]  # Bob还在Alice前面

6. 总结

Python 的 sorted() 是一个功能全面、性能优秀、接口友好的内置排序函数,核心要点如下:

  1. 通用性强:接受所有可迭代对象,返回新列表(不修改原输入)
  2. 灵魂参数 key:传入函数自定义排序依据,支持 lambda 简化、operator 提速
  3. 附加参数 reverse=True:快速实现降序
  4. 多级排序:利用 Python 元组的逐位比较特性
  5. 稳定性:相等 key 的元素保留原相对位置

掌握了这些,日常开发中99%的排序需求都能轻松搞定~