为什么在Python 3中无法像Python 2一样使用__cmp__方法?

79 投票
4 回答
56459 浏览
提问于 2025-04-17 07:06

下面这段代码

class point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def dispc(self):
        return ('(' + str(self.x) + ',' + str(self.y) + ')')

    def __cmp__(self, other):
        return ((self.x > other.x) and (self.y > other.y))

在Python 2中运行得很好,但在Python 3中我遇到了一个错误:

>>> p=point(2,3)
>>> q=point(3,4)
>>> p>q
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: point() > point()

它只适用于 ==!=

4 个回答

12

在Python3中,有六种丰富的比较操作符

__lt__(self, other) 
__le__(self, other) 
__eq__(self, other) 
__ne__(self, other) 
__gt__(self, other) 
__ge__(self, other) 

必须单独提供。不过,可以通过使用 functools.total_ordering 来简化这个过程。

但是,这样做往往会让代码变得难以阅读和不太实用。你仍然需要把类似的代码放在两个函数里,或者使用一个额外的辅助函数。

所以我通常更喜欢使用下面展示的混入类 PY3__cmp__。这个类重新建立了单一的 __cmp__ 方法框架,这在大多数情况下都非常清晰和实用。你仍然可以重写某些特定的丰富比较。

你的例子就可以变成:

 class point(PY3__cmp__):
      ... 
      # unchanged code

PY3__cmp__ 混入类:

PY3 = sys.version_info[0] >= 3
if PY3:
    def cmp(a, b):
        return (a > b) - (a < b)
    # mixin class for Python3 supporting __cmp__
    class PY3__cmp__:   
        def __eq__(self, other):
            return self.__cmp__(other) == 0
        def __ne__(self, other):
            return self.__cmp__(other) != 0
        def __gt__(self, other):
            return self.__cmp__(other) > 0
        def __lt__(self, other):
            return self.__cmp__(other) < 0
        def __ge__(self, other):
            return self.__cmp__(other) >= 0
        def __le__(self, other):
            return self.__cmp__(other) <= 0
else:
    class PY3__cmp__:
        pass
17

这是Python 3中一个重要且刻意的变化。想了解更多细节,可以查看这里

  • 在Python 3中,当你用比较运算符(比如 <, <=, >=, >)比较两个没有明确顺序的东西时,会出现一个叫 TypeError 的错误。举个例子,像 1 < ''(数字1和空字符串比较)、0 > None(数字0和空值比较)或者 len <= len(长度和长度比较)这样的表达式都不再有效。比如说,比较 None < None 现在会引发 TypeError,而不是返回 False。这也意味着,如果你有一个混合类型的列表,排序就没有意义了,因为所有元素必须可以相互比较。需要注意的是,这个规则不适用于 ==!= 运算符:不同类型的对象总是被认为是不相等的。
  • builtin.sorted()list.sort() 这两个函数不再接受 cmp 参数(也就是比较函数)。现在要使用 key 参数。需要注意的是,keyreverse 这两个参数现在只能作为关键字参数使用。
  • cmp() 函数可以认为是已经消失了,__cmp__() 这个特殊方法也不再支持。现在要进行排序时,使用 __lt__(),比较相等时用 __eq__(),如果需要其他复杂的比较也可以用相应的方法。(如果你真的需要 cmp() 的功能,可以用表达式 (a > b) - (a < b) 来代替 cmp(a, b)。)
109

在Python 3中,你需要提供一些比较的方法来进行排序,这些方法包括 __lt__(小于)、__gt__(大于)、__le__(小于等于)、__ge__(大于等于)、__eq__(等于)和 __ne__(不等于)。你可以查看 PEP 207 -- Rich Comparisons 来了解更多。

__cmp__ 这个方法不再使用


更具体地说,__lt__ 方法需要接收两个参数:selfother,并且需要返回 self 是否小于 other。举个例子:

class Point(object):
    ...
    def __lt__(self, other):
        return ((self.x < other.x) and (self.y < other.y))

(这个比较实现并不合理,但很难看出你想要表达的意思。)

所以如果你有以下情况:

p1 = Point(1, 2)
p2 = Point(3, 4)

p1 < p2

这将等同于:

p1.__lt__(p2)

这会返回 True

如果两个点相等,__eq__ 会返回 True,否则返回 False。其他方法的工作方式类似。


如果你使用 functools.total_ordering 装饰器,你只需要实现例如 __lt____eq__ 方法:

from functools import total_ordering

@total_ordering
class Point(object):
    def __lt__(self, other):
        ...

    def __eq__(self, other):
        ...

撰写回答