当前位置: 代码网 > it编程>前端脚本>Python > Python多级排序之混合升降序、None值与itemgetter提速

Python多级排序之混合升降序、None值与itemgetter提速

2026年09月14日 Python 我要评论
有一批订单数据要排序:先按客户等级从高到低,等级相同再按下单金额从高到低,金额也相同就按下单时间从早到晚。听上去简单,但真上手你会发现 sorted(orders, reverse=true) 根本满

有一批订单数据要排序:先按客户等级从高到低,等级相同再按下单金额从高到低,金额也相同就按下单时间从早到晚。听上去简单,但真上手你会发现 sorted(orders, reverse=true) 根本满足不了——因为「时间」那一级是升序,其它两级是降序,reverse 是全局开关,没法只对某一级生效。

这篇就把多级排序里最容易踩的几个坑一次讲清:多字段怎么排、升降序怎么混、none 值怎么办、以及为什么 operator.itemgetterlambda 又快又清楚。

先看朴素写法为什么不够用

初学者常见的第一反应是排两次:

orders = [
    {"name": "a", "level": 2, "amount": 300, "ts": 1005},
    {"name": "b", "level": 3, "amount": 300, "ts": 1002},
    {"name": "c", "level": 3, "amount": 500, "ts": 1009},
    {"name": "d", "level": 2, "amount": 300, "ts": 1001},
]

# 想先按 level 降序,再按 amount 降序 —— 于是排两次
r = sorted(orders, key=lambda o: o["amount"], reverse=true)
r = sorted(r, key=lambda o: o["level"], reverse=true)

这段代码其实是对的,但很多人不知道它为什么对,也不知道它的边界。它能工作,靠的是 python 排序的一个关键性质:稳定排序(stable sort)——两个 key 相等的元素,排序后它们的相对顺序保持不变。所以「最后排的字段是最主要的排序依据」,你得从次要字段往主要字段倒着排

问题来了:字段一多,排好几趟不但绕,性能也差(每趟都是一次完整 o(n log n))。而且一旦某一级要升序、另一级要降序,reverse 全局开关就彻底抓瞎。

正解一:一个 key 返回元组,一次排完

多级排序的标准做法,是让 key 函数返回一个元组。元组比较天生就是「逐元素、从左到右」:先比第 0 个,相等再比第 1 个,以此类推——这正是多级排序的语义。

# 全部升序时,一个元组搞定,一次排序
r = sorted(orders, key=lambda o: (o["level"], o["amount"], o["ts"]))

那如果 level、amount 要降序,ts 要升序呢?对数值字段,最简单的技巧是取负号让它反向:

r = sorted(orders, key=lambda o: (-o["level"], -o["amount"], o["ts"]))
for o in r:
    print(o["name"], o["level"], o["amount"], o["ts"])
# c 3 500 1009
# b 3 300 1002
# a 2 300 1005
# d 2 300 1001

-level-amount 让这两级变成降序,ts 保持升序,一趟排完,清晰又高效。

字符串字段要降序怎么办?取负号行不通

-o["name"] 会直接 typeerror,字符串没法取负。这时有两种思路。

思路一:分两趟排,利用稳定性。把要降序的字符串字段单独拎出来,作为最主要的一级最后排:

data = [
    {"city": "北京", "score": 90},
    {"city": "北京", "score": 85},
    {"city": "上海", "score": 88},
]
# 想 city 降序、score 降序
r = sorted(data, key=lambda x: -x["score"])          # 先排次要键
r = sorted(r, key=lambda x: x["city"], reverse=true)  # 再排主要键(字符串降序用 reverse)

思路二:用 functools.cmp_to_key 写自定义比较。当排序规则复杂到没法用「取负 + 元组」表达时(比如按业务字典序、多字段各自方向不同且含字符串),自定义 comparator 最直白:

from functools import cmp_to_key

def cmp(a, b):
    # 返回负数表示 a 排在前,正数表示 b 排在前,0 表示相等
    if a["city"] != b["city"]:
        return -1 if a["city"] > b["city"] else 1  # city 降序
    return b["score"] - a["score"]                  # score 降序

r = sorted(data, key=cmp_to_key(cmp))

cmp_to_key 灵活,但每次比较都要调一次 python 函数,数据量大时明显更慢——能用元组 key 就别用它。

用 itemgetter 替代 lambda:更快也更清楚

当所有字段方向一致时,operator.itemgetter 是比 lambda 更好的选择。它是 c 实现的,取多个字段直接返回元组,省掉了 python 层的函数调用开销:

from operator import itemgetter

# 等价于 key=lambda o: (o["level"], o["amount"]),但更快
r = sorted(orders, key=itemgetter("level", "amount"))

对象属性排序则用 attrgetter:

from operator import attrgetter
from dataclasses import dataclass

@dataclass
class user:
    name: str
    age: int

users = [user("tom", 30), user("amy", 25), user("tom", 22)]
r = sorted(users, key=attrgetter("name", "age"))  # 先按 name 再按 age

简单实测感受下差距(百万级数据,itemgetter 通常比等价 lambda 快 20%~40%):

import timeit
data = [{"a": i % 100, "b": i} for i in range(1_000_000)]

t1 = timeit.timeit(lambda: sorted(data, key=lambda x: (x["a"], x["b"])), number=3)
t2 = timeit.timeit(lambda: sorted(data, key=itemgetter("a", "b")), number=3)
print(f"lambda: {t1:.3f}s  itemgetter: {t2:.3f}s")

注意 itemgetter 只能整体升序或整体降序(配合 reverse),它不解决「混合方向」——混合方向还是回到「取负元组」或分趟排。

别忽略 none:排序里最常见的崩溃

真实数据里字段常有 none,而 none 和数字、字符串都没法比较,sorted 会直接抛 typeerror: '<' not supported between instances of 'nonetype' and 'int'

稳妥做法是在 key 里把 none 归一化到一个边界值,让它统一排在最前或最后:

rows = [{"score": 90}, {"score": none}, {"score": 70}]

# none 视为 -inf,升序时排最前;想排最后就用 float("inf")
r = sorted(rows, key=lambda x: (x["score"] is none, x["score"] or 0))

这里用了个小技巧:元组第一位放 x["score"] is none(布尔值 false < true),保证所有 none 被挤到一侧;第二位再对非 none 的值正常排。这样既不崩,none 的位置也完全可控。

小结

  • 多级排序的正解是一个 key 返回元组,元组逐元素比较天然就是多级语义,一趟排完,别排好几趟。
  • 数值字段要降序用取负号;字符串字段降序用 reverse(分趟排)或 cmp_to_key
  • 混合升降序时,reverse 这个全局开关帮不上忙,靠取负元组或自定义 comparator。
  • 字段方向一致时优先用 itemgetter/attrgetter,c 实现,比 lambda 更快更清晰。
  • 数据里有 none 一定要在 key 里归一化,否则 typeerror 当场崩。

一句话记忆:多级排序就是「让 key 返回元组」,降序靠取负,none 靠归一化。

到此这篇关于python多级排序之混合升降序、none值与itemgetter提速的文章就介绍到这了,更多相关python多级排序实战内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com