__lt__而不是__cmp__
Python 2.x有两种方法来重载比较运算符__cmp__
或“丰富的比较运算符”,如__lt__
。 据说比较超负荷的比较优先,但为什么这样呢?
丰富的比较运算符更容易实现,但是您必须以几乎相同的逻辑来实现其中的几个运算符。 但是,如果你可以使用内build的cmp
和tuple命令,那么__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__
?
我知道<
, <=
, ==
等操作符可以被重载用于其他目的,并且可以返回任何他们喜欢的对象。 我并不是在质疑这种方法的优点,而仅仅是关于使用这些运算符进行比较的意义上的差异,这些差异意味着数字。
更新:正如克里斯托弗指出的 , cmp
正在3.x中消失。 是否有任何替代品,使得执行比较像上面的__cmp__
一样简单?
是的,很容易实现一切,如__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 __lt__
(如果有的话)。 类装饰器是非常相似的,只是插入类似的function,作为它装饰的新类的属性(结果可能在运行时显微镜更快,在内存方面同样微小的成本)。
当然,如果你的类有一些特别快的方法来实现(例如) __ne__
和__ne__
,它应该直接定义它们,所以mixin的版本不会被使用(例如, dict
的情况就是这样) – 实际上__ne__
可能被定义为便于:
def __ne__(self, other): return not self == other
但是在上面的代码中,我想保持仅使用<
;-)的令人愉快的对称性。 至于为什么__cmp__
必须去,因为我们有__lt__
和朋友,为什么要另一种不同的方式来做同样的事情呢? 在每个Python运行时(Classic,Jython,IronPython,PyPy,…)中,它都是非常重要的。 那些肯定不会有bug的代码就是那些不存在的代码 – 从这个Python的原则来看,应该有一个理想的方式来完成一个任务(C在“C的精神”部分有相同的原则ISO标准,顺便说一句)。
这并不意味着我们会阻止事物的发展(比如mixins和类装饰器在某些用途上是近似等价的),但是这绝对意味着我们不想在编译器和/或冗余地存在的运行时间仅仅是为了支持多个等同的方法来执行完全相同的任务。
进一步编辑:实际上有一种更好的方法来为许多类提供比较和散列,包括问题中的一个__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__
特殊方法地址。
为了简化这种情况,Python 2.7 + / 3.2 +中有一个类装饰器, functools.total_ordering ,可以用来实现Alex的build议。 来自文档的示例:
@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()))
这由PEP 207 – 丰富的比较涵盖
另外, __cmp__
在python 3.0中消失。 (请注意,它不在http://docs.python.org/3.0/reference/datamodel.html上,但它在http://docs.python.org/2.7/reference/datamodel.html上; )
受到Alex Martelli的ComparableMixin
& KeyedMixin
答案的启发,我想出了以下的mixin。 它允许您实现一个_compare_to()
方法,该方法使用与KeyedMixin
相似的基于键的比较,但允许您的类根据other
typesselect最有效的比较键。 (请注意,这个mixin对于可以testing平等但不sorting的对象没有多大帮助)。
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
(编辑6/17/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