Python total_ordering:比较方法补齐了,为什么异类对象仍应返回 NotImplemented

前天 3阅读

给优先级对象写了 __eq__ 和 __lt__,再加 total_ordering,就能使用小于等于和大于等于。随后有人把对象与普通整数比较,方法里的 other.value 报了错。装饰器只负责根据已有比较方法补齐其他方法,不会替业务决定哪些类型可以比较,也不会自动把整数包装成你的对象。

这个例子把比较域明确限制为 Rank,避免偷偷接受长得相似的对象。将代码保存为 demo.py,运行 python demo.py,只需标准库。为了看见反向比较的机会,另写了一个教学用 Ceiling 类;它不是业务排序规则,而是让“本方不处理之后,另一方还能响应”变成可验证的结果。

Python total_ordering:比较方法补齐了,为什么异类对象仍应返回 NotImplemented

AI生成概念插图:同一组数值条按高度排序,异类圆环保留在另一侧,表示比较域需要明确;不是软件界面或运行截图。

from functools import total_ordering

@total_ordering
class Rank:
    def __init__(self, value):
        self.value = value

    def __eq__(self, other):
        if type(other) is not Rank:
            return NotImplemented
        return self.value == other.value

    def __lt__(self, other):
        if type(other) is not Rank:
            return NotImplemented
        return self.value < other.value

class Ceiling:
    def __gt__(self, other):
        if type(other) is Rank:
            return True
        return NotImplemented

a, b = Rank(2), Rank(5)
assert a < b and a <= b and b > a and b >= a
print("same type:", a < b, a <= b, b > a, b >= a)
assert a.__le__(5) is NotImplemented
assert (a == 5) is False
assert (a < Ceiling()) is True
print("foreign equality:", a == 5)
print("reflected comparison:", a < Ceiling())
try:
    a <= 5
except TypeError:
    print("foreign ordering: TypeError")
else:
    raise AssertionError("unsupported ordering accepted")

不支持与结果为假是两个状态

同类型的四项比较均为 True,其中小于等于、大于和大于等于是装饰器补出的行为。直接调用 a.__le__(5) 得到 NotImplemented,表示该方法没有为这组类型定义关系。如果返回 False,就等于已经宣布“小于等于不成立”,解释器失去了继续按比较协议尝试另一方的信号。

NotImplemented 是一个特殊返回值,不是需要抛出的异常,也不是 NotImplementedError。业务方法应返回它,通常不应自行把它转成布尔值。用 is 检查实验结果,可以清楚表达我们在验证协议信号,而不是在比较对象内容。

让解释器完成比较协作

a 小于 Ceiling 时,Rank 的小于方法先表示不支持,解释器再尝试另一方的大于方法,于是得到 True。小于与大于是互为反向的配对,小于等于与大于等于也是一对。更一般的规则还包含子类优先处理的情况,所以不要把所有比较简化成永远先调左侧再调右侧。

对于 a 小于等于整数五,两边都不能完成这项排序,最后抛出 TypeError。相等比较有自己的回退方式,双方不支持时会退回身份比较,因此 a == 5 为 False。应用不能据此断言“既然相等能返回假,排序也该返回假”;两类运算处理不支持的末端规则并不相同。

本例使用精确类型判断,是为了只讨论一种明确的值对象。如果计划支持子类或另一个数值类型,应同时设计左右操作数的规则,并测试对称性、传递性以及相等关系。随意用 hasattr 认一个 value 字段,可能把单位不同的数值、状态对象和真正优先级混为同一比较域。

total_ordering 不会覆盖类或父类已经提供的比较方法,也无法修复彼此矛盾的 __eq__ 与 __lt__。先验证相同值、前后顺序和异类输入,再检查新增方法。若性能分析确实发现补出的方法成为瓶颈,可以自行实现全部比较;不要在缺少测量时把便利工具的额外调用当成首要问题。

资料核对日期:2026年10月2日(北京时间)。示例在本地CPython 3.12.14实际运行并通过断言,结果对应文中固定输入。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。