Python:比较协议

比较协议(Rich Comparison Protocol)是 Python 在处理大小比较与相等性判断时所遵循的一套核心规则。它规定了解释器在遇到 a < b、a == b、a >= b 等比较语法时,应当如何判定参与对象是否可以建立比较语义,以及在判定成立后,沿何种严格顺序展开执行路径。

理解比较协议,关键不在于记住若干特殊方法名,而在于把握一个更根本的逻辑:比较运算并不是对象主动执行的能力,而是解释器在比较语法语境下所采用的一套解释规则。

对象是否"可以比较",并非其固有属性,而是解释器在特定语法结构中,对其类型结构应用比较协议后的判定结果。

一、什么是比较协议

在 Python 中,比较运算符包括:<、<=、>、>=、==、!= 等。

当解释器遇到如下语法:

css 复制代码
a < b

它首先识别这是一次比较语法结构,然后触发比较协议解释路径。

比较协议是一套统一规则,但在不同运算符参与时,会沿不同的语义分支展开。例如"小于"与"相等"在语义层面并不等价,但它们共享同一协议分派模型。

比较协议并不是运行期实体,也不是某种接口类型。它存在于解释器的规则体系之中。

二、比较协议的方法构成

比较协议的核心方法共有六个,对应六种比较运算:

php 复制代码
__lt__(self, other)   # <__le__(self, other)   # <=__gt__(self, other)   # >__ge__(self, other)   # >=__eq__(self, other)   # ==__ne__(self, other)   # !=

这些方法在类体执行阶段被创建为函数对象,并存储于类对象的字典中。它们不会因为名称特殊而自动生效。

比较协议不会通过通用属性查找路径(如 getattribute),而是由解释器直接读取类型对象的比较槽位结构(在 CPython 中对应 tp_richcompare)。

三、自定义比较语义(以小于为例)

比较协议允许用户通过定义特殊方法参与关系语义的建立。

例如:

ruby 复制代码
class Score:    def __init__(self, value):        self.value = value
    def __lt__(self, other):        if isinstance(other, Score):            return self.value < other.value        return NotImplemented

使用:

makefile 复制代码
s1 = Score(60)s2 = Score(80)
print(s1 < s2)   # True

自定义比较并不是"给对象添加排序能力",而是在比较语境(如 s1 < s2)中,为解释器提供一个可被选中的协议入口。

四、实例属性不会影响比较协议

与大多数协议一致,比较协议的判定发生在类型层面,而不是实例字典。

例如:

python 复制代码
class A:    pass
a = A()a.__lt__ = lambda other: True
b = A()a < b   # TypeError

尽管实例 a 拥有名为 lt 的属性,解释器在比较语境中不会通过普通属性访问机制进行判定,而是读取类型层的比较入口。

因此,比较能力来自类型结构,而非实例属性。

五、比较运算的严格分派顺序

以表达式:

css 复制代码
a < b

为例,其解释路径如下:

1、若 type(a) 与 type(b) 不同,且 type(b) 是 type(a) 的真子类:

→ 按子类优先原则,优先尝试:

css 复制代码
type(b).__gt__(b, a)

注意:对于 <,反向方法不是 rlt,而是 gt。因为 a < b 等价于 b > a。

2、若未触发子类优先规则,则进入标准正向路径:

css 复制代码
type(a).__lt__(a, b)

3、若返回 NotImplemented,则尝试反向路径:

css 复制代码
type(b).__gt__(b, a)

4、若仍返回 NotImplemented,抛出 TypeError。

说明:

(1)比较协议与数值运算协议一样,存在"对称协商机制"。不同的是,比较的对称性通过"对偶方法"实现,而非 r-方法命名体系。

(2)同样支持子类优先原则,右操作数的候选可能因子类关系而被优先提升。

因此,a < b 并不是简单调用某个方法,而是在比较语法语境下触发一次受规则控制的协议分派过程。

六、相等性比较的特殊性

相等运算,比如:

ini 复制代码
a == b

与大小比较不同,其失败处理方式具有特殊性。

解释路径为:

1、尝试 type(a).eq(a, b);

2、若返回 NotImplemented,尝试 type(b).eq(b, a);

3、若仍为 NotImplemented,则解释器退回到默认身份比较语义(等价于 a is b)。若两个对象身份相同,返回 True;否则返回 False。

因此,== 运算通常不会抛出 TypeError;若双方都不提供相等语义,则回退到比较对象身份。

这一点与 <、> 等运算不同,是比较协议中一个重要差异。

!= 的语义通常由 ne 决定;若未实现 ne,则解释器通常会基于 eq 的结果进行逻辑取反。

七、排序与比较协议

诸如:

php 复制代码
sorted(iterable)

或列表的 .sort() 方法,本质上依赖比较协议中的 < 运算。

内部排序算法在比较两个元素时,解释器会触发 < 的比较协议路径。

因此,若对象未定义 < 语义或协商失败,排序操作将抛出 TypeError。

这再次说明,排序并不是对象"具有排序能力",而是解释器在排序算法中反复触发比较协议。

八、比较协议的典型应用场景

比较协议贯穿多个核心语境:

1、条件判断(if / while)

2、排序与堆结构

3、集合与字典中的相等性判定(结合哈希协议)

4、去重与相等性判定

5、自定义数据结构的有序语义

这些场景的共同点在于:对象并未"执行比较",而是在比较语法语境中被解释器按协议规则解释为可比较对象。

📘 小结

比较协议不是对象模型中的实体,而是一组由解释器遵循的比较规则。它规定了解释器在比较语法语境中如何判定对象是否可比较、如何在类型结构中寻找语义入口,以及在协商失败时如何处理。对象是否"可比较",并不取决于名称或类别标签,而取决于解释器在特定语境下对其类型结构应用比较协议后的结果。

"点赞有美意,赞赏是鼓励"

相关推荐
夜来小雨2 小时前
BGP高级特性-RR路由反射器
网络·智能路由器
敲代码的哈吉蜂2 小时前
haproxy的算法——静态算法
linux·运维·服务器·算法
夜来小雨2 小时前
第二章 网络安全监督
网络·安全
8125035332 小时前
连接追踪:实现细节
网络
wuqingshun3141592 小时前
说一下JVM内存结构
java·开发语言·jvm
Web极客码2 小时前
WordPress 被植入隐藏管理员后门?清理实战分析
服务器·网络·wordpress
33三 三like2 小时前
高精度计算
开发语言·c++·算法
Hello.Reader2 小时前
Tauri 项目结构前端壳 + Rust 内核,怎么协作、怎么构建、怎么扩展
开发语言·前端·rust
头发那是一根不剩了2 小时前
Linux 常用服务器命令
linux·运维·服务器