__lt__而不是__cmp__

时间:2009-06-30 00:55:45

标签: python operator-overloading

Python 2.x有两种方法可以重载比较运算符__cmp__或“丰富的比较运算符”,例如__lt__据说富裕的比较超载是首选,但为什么会这样呢?

丰富的比较运算符更容易实现,但必须使用几乎相同的逻辑实现其中的几个。但是,如果您可以使用内置cmp和元组排序,则__cmp__变得非常简单并完成所有比较:

class A(object):
  def __init__(self, name, age, other):
    self.name = name
    self.age = age
    self.other = other
  def __cmp__(self, other):
    assert isinstance(other, A) # assumption for this example
    return cmp((self.name, self.age, self.other),
               (other.name, other.age, other.other))

这种简单性似乎比重载所有6(!)丰富的比较要好得多。 (但是,如果你依赖于“交换的论证”/反映的行为,你可以把它归结为“只是”4,但这导致并发症的净增加,在我的拙见中。)

如果我只是超载__cmp__,我是否需要了解任何无法预料的陷阱?

我理解<<===等操作符可以为其他目的重载,并且可以返回他们喜欢的任何对象。我不是在询问这种方法的优点,而是仅仅考虑使用这些运算符进行比较时的差异,这与它们对数字的意义相同。

更新:正如Christopher pointed outcmp在3.x中消失。 有没有其他方法可以让实施比较变得像上面__cmp__一样简单?

5 个答案:

答案 0 :(得分:84)

是的,很容易实现所有内容,例如: __lt__带有一个mixin类(或者一个元类,或者一个类装饰器,如果你的味道是这样的话)。

例如:

class ComparableMixin:
  def __eq__(self, other):
    return not self<other and not other<self
  def __ne__(self, other):
    return self<other or other<self
  def __gt__(self, other):
    return other<self
  def __ge__(self, other):
    return not self<other
  def __le__(self, other):
    return not other<self

现在你的类可以只定义__lt__并从ComparableMixin中多次继承(在需要的任何其他基础之后,如果有的话)。类装饰器非常相似,只是插入类似的函数作为它正在装饰的新类的属性(结果在运行时可能在显微镜下更快,内存方面的成本相同)。

当然,如果你的类有一些特别快速的实现方法(例如)__eq____ne__,它应该直接定义它们以便mixin的版本不被使用(例如,那就是dict)的情况 - 事实上__ne__可能会被定义为促进它:

def __ne__(self, other):
  return not self == other

但是在上面的代码中,我希望仅使用< ;-)保持令人愉悦的对称性。 至于为什么__cmp__必须去,因为我们 __lt__和朋友,为什么要采用另一种不同的方式来做同样的事情呢?它在每个Python运行时(Classic,Jython,IronPython,PyPy,......)中都非常重要。 肯定没有错误的代码是不存在的代码 - 而Python的原则应该是理想情况下执行任务的一种明显方式(C具有相同的原则) ISO标准的“C精神”部分,btw)。

这并不意味着我们会竭尽全力禁止某些事情(例如,某些用途的mixins和类装饰器之间几乎相等),但它肯定确实意味着我们不会喜欢在冗余存在的编译器和/或运行时中携带代码,以支持多种等效方法来执行完全相同的任务。

进一步编辑:实际上有更好的方法为许多类提供比较和散列,包括问题中的一个 - __key__方法,正如我在对问题的评论中提到的那样。因为我从来没有为它编写PEP,所以如果你喜欢的话,你现在必须使用Mixin(&amp; c)实现它:

class KeyedMixin:
  def __lt__(self, other):
    return self.__key__() < other.__key__()
  # and so on for other comparators, as above, plus:
  def __hash__(self):
    return hash(self.__key__())

实例与其他实例的比较很常见,将每个实例与一些字段进行比较 - 然后,哈希应该在完全相同的基础上实现。 __key__特殊方法直接解决了这个问题。

答案 1 :(得分:44)

为了简化这种情况,在Python 2.7 + / 3.2 +,functools.total_ordering中有一个类装饰器,可用于实现Alex建议的内容。来自文档的示例:

@total_ordering
class Student:
    def __eq__(self, other):
        return ((self.lastname.lower(), self.firstname.lower()) ==
                (other.lastname.lower(), other.firstname.lower()))
    def __lt__(self, other):
        return ((self.lastname.lower(), self.firstname.lower()) <
                (other.lastname.lower(), other.firstname.lower()))

答案 2 :(得分:9)

这由PEP 207 - Rich Comparisons

涵盖

此外,__cmp__在python 3.0中消失了。 (请注意,它不在http://docs.python.org/3.0/reference/datamodel.html上,但它位于http://docs.python.org/2.7/reference/datamodel.html

答案 3 :(得分:0)

(编辑于2017年6月17日编辑以考虑评论。)

我在上面尝试了类似的mixin答案。我遇到了“无”的麻烦。这是一个修改版本,处理与“无”的相等比较。 (我认为没有理由因为缺乏语义而无法与None进行不等式比较):


class ComparableMixin(object):

    def __eq__(self, other):
        if not isinstance(other, type(self)): 
            return NotImplemented
        else:
            return not self<other and not other<self

    def __ne__(self, other):
        return not __eq__(self, other)

    def __gt__(self, other):
        if not isinstance(other, type(self)): 
            return NotImplemented
        else:
            return other<self

    def __ge__(self, other):
        if not isinstance(other, type(self)): 
            return NotImplemented
        else:
            return not self<other

    def __le__(self, other):
        if not isinstance(other, type(self)): 
            return NotImplemented
        else:
            return not other<self    

答案 4 :(得分:0)

受到Alex Martelli的ComparableMixin&amp; KeyedMixin回答,我想出了以下的mixin。 它允许您实现单个_compare_to()方法,该方法使用基于密钥的比较 与KeyedMixin类似,但允许您的类根据other的类型选择最有效的比较键。 (注意,这个mixin对于可以测试相等而不是顺序的对象没有多大帮助)。

class ComparableMixin(object):
    """mixin which implements rich comparison operators in terms of a single _compare_to() helper"""

    def _compare_to(self, other):
        """return keys to compare self to other.

        if self and other are comparable, this function 
        should return ``(self key, other key)``.
        if they aren't, it should return ``None`` instead.
        """
        raise NotImplementedError("_compare_to() must be implemented by subclass")

    def __eq__(self, other):
        keys = self._compare_to(other)
        return keys[0] == keys[1] if keys else NotImplemented

    def __ne__(self, other):
        return not self == other

    def __lt__(self, other):
        keys = self._compare_to(other)
        return keys[0] < keys[1] if keys else NotImplemented

    def __le__(self, other):
        keys = self._compare_to(other)
        return keys[0] <= keys[1] if keys else NotImplemented

    def __gt__(self, other):
        keys = self._compare_to(other)
        return keys[0] > keys[1] if keys else NotImplemented

    def __ge__(self, other):
        keys = self._compare_to(other)
        return keys[0] >= keys[1] if keys else NotImplemented