Python 中 list.sort 和 sorted 有什么区别?
简化版
list.sort() 是列表的原地排序方法,会修改原列表并返回 None;sorted() 是内置函数,接收任意可迭代对象,返回一个新的排序列表。二者都支持 key 和 reverse,Python 排序是稳定排序。
详细版
示例:
nums = [3, 1, 2]
result = nums.sort()
print(nums) # [1, 2, 3]
print(result) # None
nums = [3, 1, 2]
new_nums = sorted(nums)
print(nums) # [3, 1, 2]
print(new_nums) # [1, 2, 3]
区别:
| 对比项 | list.sort() | sorted() |
|---|---|---|
| 是否修改原对象 | 是 | 否 |
| 返回值 | None | 新列表 |
| 适用对象 | list | 任意可迭代对象 |
| 是否支持 key/reverse | 支持 | 支持 |
按字段排序:
users = [{"name": "Tom", "age": 18}, {"name": "Jerry", "age": 16}]
users.sort(key=lambda user: user["age"])
稳定排序意味着:如果两个元素 key 相等,它们原来的相对顺序会保留。
完整版教学
一、list.sort() 是原地排序
list.sort() 修改原列表:
nums = [3, 1, 2]
nums.sort()
print(nums) # [1, 2, 3]
它返回 None,不是排序后的列表:
nums = [3, 1, 2]
nums = nums.sort()
print(nums) # None
这个设计是为了提醒你:这个方法是原地修改,不要把它当成创建新列表的函数。
原地排序的好处是少创建一个外层列表,适合你确定不再需要原顺序的场景。坏处也很明显:调用后原列表顺序被改变,其他引用这个列表的代码也会看到变化。比如多个变量指向同一个列表时,a.sort() 会让 b 看到排序后的结果。
a = [3, 1, 2]
b = a
a.sort()
print(b) # [1, 2, 3]
二、sorted() 返回新列表
sorted() 不修改原可迭代对象,而是返回新列表:
nums = [3, 1, 2]
new_nums = sorted(nums)
print(nums) # [3, 1, 2]
print(new_nums) # [1, 2, 3]
它还能处理 tuple、set、dict keys、生成器等可迭代对象:
sorted((3, 1, 2))
sorted({"b", "a", "c"})
sorted({"b": 2, "a": 1})
返回值始终是 list。
这意味着 sorted() 适合保留原数据、对非 list 可迭代对象排序、或者函数式地生成排序结果。比如 sorted({"b": 2, "a": 1}) 排的是 dict 的 key,返回 ["a", "b"];如果想按 value 排,要显式使用 items() 和 key 函数。
| 对比项 | list.sort() | sorted() |
|---|---|---|
| 修改原对象 | 是 | 否 |
| 返回值 | None | 新 list |
| 输入类型 | list | 任意可迭代对象 |
| 内存 | 更省外层列表 | 需要新列表 |
三、key 参数是排序的灵魂
key 接收一个函数,用来提取排序依据:
words = ["python", "go", "java"]
print(sorted(words, key=len)) # ['go', 'java', 'python']
对象列表排序:
users = [
{"name": "Tom", "age": 18},
{"name": "Jerry", "age": 16},
]
sorted_users = sorted(users, key=lambda u: u["age"])
多字段排序可以返回 tuple:
sorted_users = sorted(users, key=lambda u: (u["age"], u["name"]))
Python 会先比 age,相等时再比 name。
key 函数会对每个元素调用一次,Python 内部会缓存 key 结果用于排序,不是每比较一次都重新调用。这个设计比老式比较函数更高效,也更容易读。复杂对象排序时,优先写清楚 key,而不是把比较逻辑散落在循环里。
from operator import itemgetter
users = [{"name": "Tom", "age": 18}, {"name": "Jerry", "age": 16}]
print(sorted(users, key=itemgetter("age")))
四、reverse 控制整体降序
nums = [1, 3, 2]
print(sorted(nums, reverse=True)) # [3, 2, 1]
如果只想某个字段降序、另一个字段升序,可以在 key 里处理:
students = [
{"name": "A", "score": 90, "age": 18},
{"name": "B", "score": 90, "age": 17},
]
sorted_students = sorted(students, key=lambda s: (-s["score"], s["age"]))
这里分数降序,年龄升序。
要注意 reverse=True 是对整个排序结果反转,不是“只让某个字段降序”。多字段混合升降序时,可以像上面那样对数字字段取负,或者分多次利用稳定排序。对于字符串字段的降序,取负不适用,常用多次稳定排序或自定义 key 变换。
五、稳定排序有什么用
稳定排序表示:两个元素排序 key 相等时,它们原来的相对顺序不变。
这让你可以分多次排序:
students.sort(key=lambda s: s["age"])
students.sort(key=lambda s: s["score"], reverse=True)
因为排序稳定,第二次按分数排时,相同分数内部仍保持第一次年龄排序后的顺序。
Python 的排序算法是 Timsort,适合处理真实世界中常见的部分有序数据。面试不一定要展开算法细节,但可以知道它是稳定排序。
数字例子:先按年龄升序排,再按分数降序排;因为第二次排序稳定,相同分数的人仍保持年龄升序。这个技巧能让混合排序逻辑更清楚,特别是某些字段要升序、某些字段要降序时。
students = [
{"name": "A", "score": 90, "age": 18},
{"name": "B", "score": 90, "age": 17},
{"name": "C", "score": 95, "age": 20},
]
students.sort(key=lambda s: s["age"])
students.sort(key=lambda s: s["score"], reverse=True)
记忆钩子:稳定排序让“先按次要字段排,再按主要字段排”成为可靠技巧。
六、常见边界:不可比较元素和 None
Python 3 不会随便比较不同类型对象。例如 [1, "2"] 排序会报错,因为 int 和 str 没有默认大小关系。包含 None 的数据也要明确排序规则,不能期待 Python 自动知道 None 应该排最前还是最后。
items = [3, None, 1]
print(sorted(items, key=lambda x: (x is None, x if x is not None else 0)))
上面 key 的第一项让非 None 排前面,None 排后面;第二项才是数值排序。实际业务里,缺失值、大小写、中文排序、自然排序都要明确规则。
七、常见误区与追问
- 误区:
sort()返回排序后的列表。 它原地修改并返回None,新列表用sorted()。 - 误区:
sorted()会保留输入类型。 它接收任意可迭代对象,但返回值始终是 list。 - 误区:
reverse=True可以单独控制某个字段降序。 它反转整个结果,混合升降序要设计 key 或分多次稳定排序。 - 追问:Python 排序稳定有什么用? key 相等时保留原相对顺序,可以分阶段排序。
- 追问:
key函数什么时候执行? 通常每个元素提取一次 key,排序过程使用 key 结果比较。 - 追问:不同类型混排为什么报错? Python 3 不定义任意类型之间的默认大小关系,需要显式 key。
八、加强记忆
把排序记成“是否改原列表”这条主线:list.sort() 改原列表、返回 None,适合不保留原顺序;sorted() 不改输入、返回新 list,适合任意可迭代对象。复杂排序用 key 提取规则,整体降序用 reverse,混合升降序和多级排序靠 tuple key 或稳定排序分步完成。