☰
Python的排序把我坑惨了,原来sort和sorted的区别这么大
2026/10/14 16:54:37 网站建设 项目流程

小郑那天写了一个排行榜功能,需要把用户按积分从高到低排序,然后展示前10名。

他拿到的数据是一个列表,里面是字典,每个字典有用户名和积分。他写了一段代码:

users = [ {"name": "Alice", "score": 85}, {"name": "Bob", "score": 92}, {"name": "Charlie", "score": 78}, ] users.sort(key=lambda u: u["score"], reverse=True) print(users)

跑出来没问题,排行榜正常显示。但过了一会儿,产品经理说:“能不能在排行榜下面再加一个‘所有用户按注册时间排序’的列表?”

小郑心想,简单。他直接对同一个users列表又调了一次排序:

users.sort(key=lambda u: u["name"]) print(users)

结果排行榜的数据乱了——原本按积分排好的顺序被覆盖了。他这才意识到:sort()是原地修改,排完一次,原列表的顺序就永久变了。如果想要两个不同顺序的列表,必须用sorted()生成新列表,而不是反复sort()同一个列表。

“我就想排个序,怎么还把数据搞丢了?”

问题的根源,是他没搞清楚sort和sorted的区别。这两个东西看起来都是排序,但行为完全不同。

先看一个最直观的对比

nums = [3, 1, 4, 1, 5, 9, 2, 6] # 方式一:sort() result1 = nums.sort() print("sort() 之后 nums:", nums) print("sort() 的返回值:", result1) # 方式二:sorted() nums2 = [3, 1, 4, 1, 5, 9, 2, 6] result2 = sorted(nums2) print("sorted() 之后 nums2:", nums2) print("sorted() 的返回值:", result2)

输出:

sort() 之后 nums: [1, 1, 2, 3, 4, 5, 6, 9] sort() 的返回值: None sorted() 之后 nums2: [3, 1, 4, 1, 5, 9, 2, 6] sorted() 的返回值: [1, 1, 2, 3, 4, 5, 6, 9]

区别非常明显:

sort()把原列表原地排好序,原列表被修改,返回值是None。

sorted()不碰原列表,返回一个新的排好序的列表。

用一个生活场景来理解。你有一摞扑克牌,想按大小整理。

sort()的做法是:直接在桌子上把这摞牌重新排好。原来的顺序没了,但你手里还是那摞牌。

sorted()的做法是:拿出另一张桌子,把这摞牌复制一份,在另一张桌子上排好。原来那摞牌纹丝不动,你手里多了一摞排好序的牌。

区别一目了然:一个改原数据,一个不改。

sort() 是列表的方法,sorted() 是内置函数

这个区别看起来是语法层面的,但它决定了两者的适用范围。

sort()是列表对象的方法,只能用于列表:

lst = [3, 1, 2] lst.sort() # 可以 tup = (3, 1, 2) tup.sort() # AttributeError: 'tuple' object has no attribute 'sort'

sorted()是Python的内置函数,接受任何可迭代对象:

sorted([3, 1, 2]) # [1, 2, 3] sorted((3, 1, 2)) # [1, 2, 3] sorted({3, 1, 2}) # [1, 2, 3] sorted("cab") # ['a', 'b', 'c'] sorted({"b": 2, "a": 1}) # ['a', 'b'],对键排序

注意:sorted()对字典排序时,默认对键排序,返回的是键的列表。

sort()只能作用于列表,sorted()可以作用于任何可迭代对象。这是两者最本质的能力差异。

为什么 sort() 返回 None?

这是Python的一个设计决策,不是bug。

sort()是原地操作,它修改了列表本身。如果它返回排序后的列表,会让人误以为原列表没变。返回None是一个明确的信号:这个操作没有产生新对象,你要的东西就在原列表里。

这个设计理念在Python里很常见。所有原地修改的方法都返回None:

lst = [1, 2, 3] print(lst.append(4)) # None print(lst.extend([5])) # None print(lst.reverse()) # None print(lst.sort()) # None

而返回新对象的方法/函数则返回结果:

print(sorted([3, 1, 2])) # [1, 2, 3] print(list(reversed([1, 2, 3]))) # [3, 2, 1]

这个规则叫“命令-查询分离”:修改数据的操作不返回值,返回值的操作不修改数据。

所以当你写出这样的代码时:

nums = [3, 1, 2] nums = nums.sort() print(nums) # None

问题不在于sort()有bug,而在于你把None赋给了nums。原列表确实排好序了,但你用None覆盖了它,把排好序的列表弄丢了。

坑一:把 sort() 的返回值赋给变量

这是最常见的错误:

nums = [3, 1, 2] nums = nums.sort() # nums 变成 None

正确的写法是直接调用,不接收返回值:

nums = [3, 1, 2] nums.sort() print(nums) # [1, 2, 3]

或者用sorted():

nums = [3, 1, 2] nums = sorted(nums) print(nums) # [1, 2, 3]

坑二:以为 sorted() 会修改原列表

反过来,有些人用了sorted(),以为原列表被改了:

nums = [3, 1, 2] sorted(nums) print(nums) # [3, 1, 2],没变

sorted()返回的新列表没有被接收,直接丢弃了。原列表纹丝不动。

正确的写法是接收返回值:

nums = [3, 1, 2] nums = sorted(nums) print(nums) # [1, 2, 3]

或者用sort()原地修改:

nums = [3, 1, 2] nums.sort() print(nums) # [1, 2, 3]

坑三:对元组或字符串用 sort()

sort()只能用于列表。如果你有一个元组想排序,只能用sorted():

tup = (3, 1, 2) tup.sort() # AttributeError result = sorted(tup) # [1, 2, 3]

字符串也一样:

s = "cab" s.sort() # AttributeError result = sorted(s) # ['a', 'b', 'c']

注意sorted("cab")返回的是字符列表,不是字符串。如果你想要排序后的字符串,需要再拼回去:

result = "".join(sorted("cab")) print(result) # "abc"

坑四:在需要保留原顺序的场景用了 sort()

这是小郑踩的坑。

users = [{"name": "Alice", "score": 85}, ...] # 按积分排序 users.sort(key=lambda u: u["score"], reverse=True) leaderboard = users # 这里 leaderboard 和 users 是同一个列表 # 后来又按名字排序 users.sort(key=lambda u: u["name"]) print(leaderboard) # 顺序被覆盖了

因为leaderboard = users只是给同一个列表起了个别名,没有复制。users.sort()修改的是同一个列表,leaderboard看到的也是修改后的结果。

正确的做法是用sorted()生成新列表:

users = [{"name": "Alice", "score": 85}, ...] leaderboard = sorted(users, key=lambda u: u["score"], reverse=True) by_name = sorted(users, key=lambda u: u["name"]) print(users) # 原顺序不变 print(leaderboard) # 按积分排序 print(by_name) # 按名字排序

这样三个列表互不影响。

排序的稳定性

Python的排序是稳定排序。意思是:如果两个元素比较相等,它们在排序后的相对顺序和排序前一致。

data = [ {"name": "Alice", "score": 85}, {"name": "Bob", "score": 92}, {"name": "Charlie", "score": 85}, ] data.sort(key=lambda x: x["score"]) print(data)

输出:

[ {"name": "Alice", "score": 85}, {"name": "Charlie", "score": 85}, {"name": "Bob", "score": 92}, ]

Alice和Charlie的积分相同,都是85。排序后Alice仍在Charlie前面,因为她们在原列表里就是这个顺序。

这个特性在多重排序时很有用。比如先按积分排序,积分相同的再按名字排序:

data.sort(key=lambda x: x["name"]) # 先按名字排 data.sort(key=lambda x: x["score"], reverse=True) # 再按积分排

因为排序是稳定的,第二次排序时,积分相同的元素会保持第一次排序后的名字顺序。这实现了“积分优先,积分相同按名字”的效果。

如果用sorted(),要注意每次都要接收新列表:

by_name = sorted(data, key=lambda x: x["name"]) by_score = sorted(by_name, key=lambda x: x["score"], reverse=True)

key 参数:排序的核心

sort()和sorted()都支持key参数。key是一个函数,接收一个元素,返回一个用于比较的值。

words = ["banana", "apple", "cherry"] words.sort(key=len) print(words) # ['apple', 'banana', 'cherry']

按长度排序,长度相同的保持原顺序(稳定排序)。

也可以按多个条件排序,用元组作为key:

data = [("Alice", 85), ("Bob", 92), ("Charlie", 85)] data.sort(key=lambda x: (x[1], x[0])) print(data)

先按积分升序,积分相同按名字升序。

如果积分要降序,名字要升序,可以这样:

data.sort(key=lambda x: (-x[1], x[0]))

用负数实现降序,是一个常见技巧。但只适用于数值。如果要对字符串降序,得用reverse=True,但那会把所有字段都降序。

reverse 参数

sort()和sorted()都支持reverse参数:

nums = [3, 1, 4, 1, 5] nums.sort(reverse=True) print(nums) # [5, 4, 3, 1, 1] result = sorted([3, 1, 4, 1, 5], reverse=True) print(result) # [5, 4, 3, 1, 1]

reverse=True表示降序。默认是升序。

注意:reverse=True和先升序再反转结果不完全一样。因为稳定排序,先升序再反转会打乱相等元素的相对顺序。而reverse=True在排序时就处理了,相等元素保持原顺序。

性能差异

sort()是原地排序,不需要额外分配列表空间。sorted()需要创建一个新列表,占用额外内存。

在时间上,两者差不多,都是O(n log n)。但sorted()多了一次复制操作,所以稍微慢一点点。

import timeit lst = list(range(1000, 0, -1)) t1 = timeit.timeit(lambda: sorted(lst), number=10000) t2 = timeit.timeit(lambda: lst.copy().sort(), number=10000) print(f"sorted(): {t1:.4f}") print(f"sort(): {t2:.4f}")

在我的机器上,sorted()大约0.6秒,sort()大约0.5秒。差距不大,但确实存在。

如果你不需要保留原列表,用sort()更省内存。如果你需要保留原列表,用sorted()。

什么时候用 sort(),什么时候用 sorted()?

判断标准很简单:

需要保留原列表的顺序:用sorted()。

不需要保留原顺序,只想把原列表排好:用sort()。

对非列表的可迭代对象排序:只能用sorted()。

在函数参数里排序:用sorted(),因为sort()会修改传入的列表,可能影响调用者。

链式调用:用sorted(),因为它返回新列表,可以继续调用其他方法:

result = sorted(data, key=lambda x: x["score"])[:10]

而sort()返回None,不能链式调用。

一个常见的误解:sorted() 比 sort() 慢很多

有些人觉得sorted()创建新列表,所以比sort()慢很多。其实不然。

sorted()确实多了一次复制,但复制是O(n),排序是O(n log n)。当n很大时,排序的时间占主导,复制的开销可以忽略。

import timeit lst = list(range(100000, 0, -1)) t1 = timeit.timeit(lambda: sorted(lst), number=100) t2 = timeit.timeit(lambda: lst.copy().sort(), number=100) print(f"sorted(): {t1:.4f}") print(f"sort(): {t2:.4f}")

在我的机器上,sorted()大约2.1秒,sort()大约2.0秒。差距只有5%左右。

所以选择sort()还是sorted(),主要看语义需求,而不是性能。

排序不可变对象

如果你有一个元组列表,想按某个字段排序,只能用sorted():

users = ( ("Alice", 85), ("Bob", 92), ("Charlie", 78), ) result = sorted(users, key=lambda u: u[1], reverse=True) print(result)

元组本身不可变,不能原地排序。sorted()返回一个新的列表,里面是排好序的元组。

回到小郑的故事

小郑最终的代码是这样的:

users = [ {"name": "Alice", "score": 85}, {"name": "Bob", "score": 92}, {"name": "Charlie", "score": 78}, ] leaderboard = sorted(users, key=lambda u: u["score"], reverse=True) by_name = sorted(users, key=lambda u: u["name"]) print("排行榜:", leaderboard) print("按名字:", by_name) print("原始数据:", users) # 顺序不变

users始终保持原始顺序,两个排序结果互不影响。

他后来在团队文档里写了一条:“sort()原地改,返回None。sorted()不改原数据,返回新列表。需要保留原顺序就用sorted(),不需要就用sort()。别把sort()的返回值赋给变量。”

这个区分看起来简单,但真到写代码的时候,很多人会随手写nums = nums.sort(),然后在下一行打印时发现是None。或者用sort()排了序,然后发现原数据的顺序被改了,导致其他依赖原顺序的逻辑出错。

记住那个扑克牌的比喻:sort()是在原桌子上重新排,sorted()是另开一张桌子排。你要保留原桌子的顺序,就用sorted();你不在乎原顺序,只想把当前桌子整理好,就用sort()。

排序本身不难,难的是想清楚“这个排序会不会影响别的地方”。想清楚了,sort和sorted就不再是坑,而是两个各司其职的工具。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询