尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Python的排序把我坑惨了,原来sort和sorted的区别这么大
小郑那天写了一个排行榜功能需要把用户按积分从高到低排序然后展示前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就不再是坑而是两个各司其职的工具。
RELATED

相关推荐

Oracle EBS ASCP方案设置测试:从数据收集到计划验证的避坑指南

Oracle EBS ASCP方案设置测试:从数据收集到计划验证的避坑指南

简介:这是一份面向Oracle EBS实施顾问与供应链计划人员的ASCP高级计划排程测试文档,以YY手机公司为真实业务背景,完整演示了从物料定义与组织分配、BOM与工艺路线搭建、来源规则及分配清单配置,到MDS主需求计划、供应链计划选项设…

📅 2026/10/14 16:54:43
PLC跨小时统计:T#5S与R_TRIG谁更稳?时间戳方案实测

PLC跨小时统计:T#5S与R_TRIG谁更稳?时间戳方案实测

前几天帮一位朋友排查包装设备的运行时长统计程序,凌晨跨整点之后数据差了17秒。我第一反应是查传感器、查信号,后来发现根本不是硬件问题,而是统计逻辑本身藏了坑。程序里混用了两种实现:一种用T#5S自复位定时器做周期累加&#…

📅 2026/10/14 16:54:43
RK3576边缘AI部署实测:主流图像分类模型INT8量化性能全解析

RK3576边缘AI部署实测:主流图像分类模型INT8量化性能全解析

做边缘AI项目的朋友,大概率都有过这种经历:硬件评估报告上写着一行“内置NPU,算力6 TOPS”,看起来挺猛;真到把自己的模型部署上去,实际帧率和预期差了不止一倍。最近一段时间,我集中测了一批主流…

📅 2026/10/14 16:54:43
MORE NEWS

更多资讯

📰

openHiTLS 社区贡献指南:CLA 签署、Issue 讨论与本地构建验证实战

密码学网络安全 【免费下载链接】openHiTLS 旨在打造算法先进、性能卓越、高效敏捷、安全可靠的密码套件,通过轻量级、可剪裁的软件技术架构满足各行业不同场景的多样化要求,让密码技术应用更简单,同时探索后量子等先进算法创新实践&#xff…

📰

视频播放异常排查:非APP层导致的黑屏卡顿根因定位

干播放器开发的,多少都经历过这种夜晚:线上反馈视频黑屏、卡顿、一直转圈,第一反应把APP代码翻了个底朝天,日志加了十几处,甚至回滚了两个版本,问题还在。后来才发现,出问题的环节根本不在APP层…

📰

玲珑轮胎登上山东台:一条轮胎凭什么跑出“山东好品”的底气

近日,玲珑轮胎登上山东电视台新闻频道《市场监管在线》栏目“好品山东‘链’成记—企业说好品”,以“质量是企业的生命线”为主题,玲珑轮胎常务副总裁孙松涛介绍了玲珑通过提高制造标准、加大研发能力、提升产品性能、严格把控质量、紧抓细节…

📰

PRAXIST 直接 CLI 运维指南:从校验、启动到观察、停止与恢复的完整生命周期

人工智能AI Agent多智能体Agent 工作流深度研究AI 评测 【免费下载链接】PRAXIST Autonomous research system for measurable, computer-executable research. 项目地址: https://gitcode.com/gh_mirrors/pr/PRAXIST 点击查看 免费下载 本文是面向运维人员&#x…

📰

ATF转换PNG/JPG工具实战:从识别文件头到DXT1解压的完整指南

简介:这是一款专门将Adobe纹理格式(ATF)转换为PNG或JPG格式的实用工具,面向Stage3D开发者、Flash/Air游戏美工及需要跨平台处理ATF纹理的图形工作者。资源包合计30个文件,压缩后约13.58MB,内含exe主程序、d…

📰

GuardianShell主动式AI野生动物哨兵:边缘计算架构与部署实战

1. 从零拆解 GuardianShell:一个主动式 AI 野生动物哨兵到底在解决什么问题第一次看到 GuardianShell 这个标题,我脑子里蹦出来的不是“又一个 AI 摄像头”,而是一个很具体的画面:凌晨两点,某片山区农场的边缘&#xf…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬