使用__lt__代替__cmp__
使用__lt__代替__cmp__
Python 2.x有两种重载比较运算符的方法,__cmp__
或 \"rich comparison operators\",如__lt__
。强烈建议使用\"rich comparison operators\",但为什么呢?
\"rich comparison operators\"更简便,但你必须要实现数个几乎相同逻辑的操作符。然而,如果可以使用内置的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个\"rich comparisons\",这种简单的方式更能够满足需求。(然而,如果你依赖\"swapped argument\"/reflected behavior,你就可以将操作符数量缩减至4个,但这会导致复杂度上升,个人意见而言。)
如果仅重载__cmp__
,是否会引起意料之外的问题?
我理解<
、<=
、==
等操作符可以用于其他目的,并且可以返回任何对象。我不是在询问此种方式的优点,而是想要了解与使用这些操作符进行比较时,其与数字的意义是否相同。
更新: 正如Christopher 所指出的,在3.x中cmp
已消失。那么是否有其他替代方案可以使实现比较操作和上述__cmp__
方法一样简单?
为了简化这个情况,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()))
嗯,用一个组合类(或元类,或类装饰器,如果你喜欢)以__lt__
为例实现一切都很容易。
例如:
class ComparableMixin: def __eq__(self, other): return not self
现在你的类只需要定义
__lt__
,并从ComparableMixin中继承(在它需要的任何其他基类后面)。类装饰器非常相似,只需将类装饰器的相似函数插入到它装饰的新类的属性中(结果在运行时可能微小地更快,在内存方面的代价同样微不足道)。当然,如果你的类有一种特别快速的方式来实现(例如)
__eq__
和__ne__
,它应该直接定义它们,以使混合版的版本不可用(例如在dict
中就是这种情况) -- 实际上,__ne__
可能很好地定义为方便这样做:def __ne__(self, other): return not self == other
但在上面的代码中,我想保持只使用
<
的对称美;-)。至于为什么
__cmp__
必须离开,既然我们已经有了__lt__
等,为什么还要保留另一种不同的方式来做完全相同的事情?这只是在每个Python运行时中带着如此沉重的负担(Classic,Jython,IronPython,PyPy等)。绝对不会出错的代码是不存在的——这就是Python原则的所在,即最好有一种明显的方法来执行任务(值得一提的是,在ISO标准的“C之灵”章节中,C也有相同的原则)。这并不意味着我们不去阻止一些事情(例如,一些用途中混合和类装饰器之间的近似等效性),但它确实意味着我们不喜欢携带编译器和/或运行时中冗余存在的代码,仅仅是为了支持多种等价的方法来执行完全相同的任务。
进一步编辑:实际上,提供比较和散列的更好方法是使用
__key__
方法,我在问题的评论中提到了这一点,这适用于许多类,包括问题中的类。由于我从未写过PEP,所以如果你喜欢它,你必须使用Mixin(&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__
特殊方法直接解决了这种需求。