Python 3.12+ 移除 cmp 参数是为了统一排序逻辑、提升性能并强制显式定义排序依据;必须用 key 函数(如 lambda 或 attrgetter)或 cmp_to_key 转换,且需妥善处理 None、NaN、时区等边界情况。

Python 3.12+ 中无法直接用 cmp 参数排序,必须转为 key 函数;否则会报 TypeError: sort() got an unexpected keyword argument 'cmp'
为什么不能直接传 cmp 函数?
从 Python 3.0 开始,list.sort() 和 sorted() 已移除 cmp 参数。这是为了统一排序逻辑、提升性能,并强制开发者显式定义“如何提取排序依据”。旧代码里类似 sorted(items, cmp=my_compare) 的写法会直接报错。
如果你看到文档或老教程还在提 cmp,基本是 Python 2 遗留内容,别照搬。
-
functools.cmp_to_key()是唯一官方兼容方案,但它只是把比较逻辑“翻译”成key可用的形式,不改变底层行为 - 纯
key方案更高效:每个对象只调用一次key函数;而cmp在排序过程中可能被反复调用数十次 - 多维度排序时,
key返回元组天然支持优先级(比如(x.priority, -x.score, x.name))
用 key 实现多字段升降序混合排序
核心技巧是:让每个维度返回可比较的值,升序用原值,降序用负数(数值)或取反(False/True)、或 reversed() 配合 tuple ——但最稳的是对数值取负,对字符串用 str.lower() 等预处理。
假设你有 User 对象,需按「部门升序 → 年龄降序 → 姓名升序(忽略大小写)」排列:
from operator import attrgetter
<h1>推荐:一行 key,清晰且无副作用</h1><p>sorted_users = sorted(users, key=lambda u: (u.dept, -u.age, u.name.lower()))</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill8054" title="python-parallelization"><img
src="https://img.php.cn/upload/skill/000/000/081/179181013081932.jpg" alt="python-parallelization" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill8054" title="python-parallelization" class="overflowclass">python-parallelization</a>
<p class="overflowclass">将顺序 Python 代码转换为并行/并发实现,用于在需要并行化 Python 代码、提升性能时使用。</p>
</div>
<a rel="nofollow" href="/xiazai/skill8054" title="python-parallelization" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><h1>或用 attrgetter 组合(适合纯属性名,不支持表达式如 -u.age)</h1><h1>sorted_users = sorted(users, key=attrgetter('dept', 'name'))</h1><h1>→ 这种写法无法实现年龄降序,得另写 lambda</h1><p></p>
- 元组各元素类型必须一致可比(比如不能混用
None和str,否则运行时报TypeError) -
u.name.lower()比u.name更安全,避免'Z' 导致排序错乱 - 如果某字段可能为
None,要提前转换,例如(u.dept or '', -u.age if u.age is not None else float('-inf'), ...)
当必须用自定义比较逻辑时:用 functools.cmp_to_key
仅在以下情况才考虑它:逻辑无法简单拆成独立字段(比如按“距离最近办公室 + 是否为 VIP”复合打分、或依赖外部状态),且你已有现成的两参数比较函数。
例如按“活跃度”排序:VIP 用户永远排前面,同为 VIP 则按登录天数;非 VIP 按发帖数:
from functools import cmp_to_key
<p>def compare_user(a, b):
if a.is_vip and not b.is_vip:
return -1
if not a.is_vip and b.is_vip:
return 1</p><h1>同为 VIP 或同为非 VIP</h1><pre class="brush:php;toolbar:false;">if a.is_vip:
return b.login_days - a.login_days # VIP:登录天数多的在前(降序)
else:
return a.post_count - b.post_count # 非 VIP:发帖多的在前(升序)sorted_users = sorted(users, key=cmp_to_key(compare_user))
- 注意
compare_user必须严格返回 -1 / 0 / 1,不能只返回布尔或任意整数(虽然 Python 会隐式转,但易出错) - 这种写法性能低于纯
key,尤其数据量大时;调试也更难——建议先尝试拆解为key元组 - 若比较逻辑含 I/O 或耗时计算,务必缓存结果(比如加
@lru_cache),否则会被重复调用
容易被忽略的边界:None、NaN、时区感知时间
真实业务中,字段为空很常见。直接参与元组排序会导致崩溃:
-
datetime字段含None?用u.updated_at or datetime.min或u.updated_at.timestamp() if u.updated_at else 0 - 浮点字段含
float('nan')?nan与任何值比较都为False,排序位置不确定;应先转为0或极大/极小值 - 字符串字段含空值?
(u.tag or '')最安全;u.tag or 'zzz'是 hack,不可靠 - 时区问题:带 tzinfo 的
datetime和不带的不能直接比;统一转为 UTC 时间戳再比较
这些不是“进阶技巧”,而是上线前必须检查的硬性条件。漏掉一个 None,线上就可能抛 TypeError 中断整个列表渲染。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










