小郑那天写了一个排行榜功能需要把用户按积分从高到低排序然后展示前10名。他拿到的数据是一个列表里面是字典每个字典有用户名和积分。他写了一段代码users [ {name: Alice, score: 85}, {name: Bob, score: 92}, {name: Charlie, score: 78}, ] users.sort(keylambda u: u[score], reverseTrue) print(users)跑出来没问题排行榜正常显示。但过了一会儿产品经理说“能不能在排行榜下面再加一个‘所有用户按注册时间排序’的列表”小郑心想简单。他直接对同一个users列表又调了一次排序users.sort(keylambda 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 sortsorted()是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里很常见。所有原地修改的方法都返回Nonelst [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(keylambda u: u[score], reverseTrue) leaderboard users # 这里 leaderboard 和 users 是同一个列表 # 后来又按名字排序 users.sort(keylambda u: u[name]) print(leaderboard) # 顺序被覆盖了因为leaderboard users只是给同一个列表起了个别名没有复制。users.sort()修改的是同一个列表leaderboard看到的也是修改后的结果。正确的做法是用sorted()生成新列表users [{name: Alice, score: 85}, ...] leaderboard sorted(users, keylambda u: u[score], reverseTrue) by_name sorted(users, keylambda 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(keylambda x: x[score]) print(data)输出[ {name: Alice, score: 85}, {name: Charlie, score: 85}, {name: Bob, score: 92}, ]Alice和Charlie的积分相同都是85。排序后Alice仍在Charlie前面因为她们在原列表里就是这个顺序。这个特性在多重排序时很有用。比如先按积分排序积分相同的再按名字排序data.sort(keylambda x: x[name]) # 先按名字排 data.sort(keylambda x: x[score], reverseTrue) # 再按积分排因为排序是稳定的第二次排序时积分相同的元素会保持第一次排序后的名字顺序。这实现了“积分优先积分相同按名字”的效果。如果用sorted()要注意每次都要接收新列表by_name sorted(data, keylambda x: x[name]) by_score sorted(by_name, keylambda x: x[score], reverseTrue)key 参数排序的核心sort()和sorted()都支持key参数。key是一个函数接收一个元素返回一个用于比较的值。words [banana, apple, cherry] words.sort(keylen) print(words) # [apple, banana, cherry]按长度排序长度相同的保持原顺序稳定排序。也可以按多个条件排序用元组作为keydata [(Alice, 85), (Bob, 92), (Charlie, 85)] data.sort(keylambda x: (x[1], x[0])) print(data)先按积分升序积分相同按名字升序。如果积分要降序名字要升序可以这样data.sort(keylambda x: (-x[1], x[0]))用负数实现降序是一个常见技巧。但只适用于数值。如果要对字符串降序得用reverseTrue但那会把所有字段都降序。reverse 参数sort()和sorted()都支持reverse参数nums [3, 1, 4, 1, 5] nums.sort(reverseTrue) print(nums) # [5, 4, 3, 1, 1] result sorted([3, 1, 4, 1, 5], reverseTrue) print(result) # [5, 4, 3, 1, 1]reverseTrue表示降序。默认是升序。注意reverseTrue和先升序再反转结果不完全一样。因为稳定排序先升序再反转会打乱相等元素的相对顺序。而reverseTrue在排序时就处理了相等元素保持原顺序。性能差异sort()是原地排序不需要额外分配列表空间。sorted()需要创建一个新列表占用额外内存。在时间上两者差不多都是O(n log n)。但sorted()多了一次复制操作所以稍微慢一点点。import timeit lst list(range(1000, 0, -1)) t1 timeit.timeit(lambda: sorted(lst), number10000) t2 timeit.timeit(lambda: lst.copy().sort(), number10000) print(fsorted(): {t1:.4f}) print(fsort(): {t2:.4f})在我的机器上sorted()大约0.6秒sort()大约0.5秒。差距不大但确实存在。如果你不需要保留原列表用sort()更省内存。如果你需要保留原列表用sorted()。什么时候用 sort()什么时候用 sorted()判断标准很简单需要保留原列表的顺序用sorted()。不需要保留原顺序只想把原列表排好用sort()。对非列表的可迭代对象排序只能用sorted()。在函数参数里排序用sorted()因为sort()会修改传入的列表可能影响调用者。链式调用用sorted()因为它返回新列表可以继续调用其他方法result sorted(data, keylambda 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), number100) t2 timeit.timeit(lambda: lst.copy().sort(), number100) print(fsorted(): {t1:.4f}) print(fsort(): {t2:.4f})在我的机器上sorted()大约2.1秒sort()大约2.0秒。差距只有5%左右。所以选择sort()还是sorted()主要看语义需求而不是性能。排序不可变对象如果你有一个元组列表想按某个字段排序只能用sorted()users ( (Alice, 85), (Bob, 92), (Charlie, 78), ) result sorted(users, keylambda u: u[1], reverseTrue) print(result)元组本身不可变不能原地排序。sorted()返回一个新的列表里面是排好序的元组。回到小郑的故事小郑最终的代码是这样的users [ {name: Alice, score: 85}, {name: Bob, score: 92}, {name: Charlie, score: 78}, ] leaderboard sorted(users, keylambda u: u[score], reverseTrue) by_name sorted(users, keylambda 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就不再是坑而是两个各司其职的工具。