导出成绩表时,需求常是“分数高的在前,同分按姓名排列,未评分的放最后”。一个 reverse=True 无法表达全部规则。把优先级翻译成排序键,再用稳定性处理平局,才能让结果既符合业务,也便于验证。
先区分复制与原地修改
sorted(iterable, key=...) 返回新列表;list.sort(key=...) 修改原列表并返回 None。不要写 rows = rows.sort(),否则变量会变成 None。新列表仍引用原来的记录对象,修改其中的字典也会影响原记录。
key 提取每条记录的比较值,正常完成的一次排序会为每个输入元素调用一次,而非每次比较都调用。昂贵的解析可以放在这里,但网络请求等有副作用的操作应提前完成。Python 排序官方指南
把规则写成从左到右的元组
缺失标记先比较
假设分数只能是整数或 None,姓名都是字符串:
1 | rows = [ |
元组按字典序比较:前一项相等,才继续看后一项。布尔值 False 排在 True 前面,所以已评分记录先出现;整数取负后,分数越高,键越小;最后再按姓名升序。
| 键的位置 | 业务含义 | 处理方式 |
|---|---|---|
| 第一项 | 未评分放最后 | score is None |
| 第二项 | 分数降序 | 已评分时取负 |
| 第三项 | 同分按姓名升序 | 原始姓名 |
缺失记录的第二项填零只是占位,它们已被第一项隔开,不会与真实零分混淆。不能用 score or 0 判断缺失,因为零分本身是有效值。
整体反转会改变所有字段
给复合键加 reverse=True,会让整个键按降序排列,不只反转分数。它仍保持相等键的稳定性,却无法直接表达“分数降序、姓名升序”。数字可取负;字符串降序则更适合分轮排序。
用稳定性组合不同方向
稳定排序保证相同键的元素保留输入中的相对次序。因此可以先排次要字段,再排主要字段。下面用姓名降序、分数升序演示:
1 | scored = [r for r in rows if r["score"] is not None] |
分数相同时,第二轮保留第一轮排好的姓名次序。这种写法适用于各字段方向不同的情况;轮数增加也意味着更多排序工作。若先排主字段,随后按次字段重排,主次关系就颠倒了。
稳定不等于跨次运行结果固定
示例中的两条 An 记录完整键相同,所以编号 2 保持在 4 前面。如果输入来自没有明确次序的数据源,稳定排序只会保留那个输入次序。需要导出结果可复现时,应追加唯一且稳定的编号作为最后一项:
1 | ordered = sorted(rows, key=lambda r: (*rank(r), r["id"])) |
先约定边界,再验证结果
不要把数字与数字字符串混排,也不要依赖 None 与整数直接比较。浮点 NaN 不遵循普通数值的大小关系,应先拒绝或归入明确的缺失类别。姓名默认按 Unicode 码点次序排列,若业务要求拼音或特定语言排序,需要另外生成相应排序键。
验证时覆盖零分、负分、全部缺失和完整键相同的记录;同时核对数量不变、优先级正确、同键次序保留。追加唯一编号后,再打乱输入检查结果是否一致。先把这些规则写成预期编号序列,比只看一张“似乎排好了”的表更可靠。