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 out,cmp
在3.x中消失。 有没有其他方法可以让实施比较变得像上面__cmp__
一样简单?
答案 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)
此外,__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