问题:__lt__代替__cmp__
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 指出的那样,cmp
它在3.x中消失了。有没有其他选择可以像上面那样轻松实现比较__cmp__
?
回答 0
是的,很容易__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运行时(经典,Jython,IronPython,PyPy等)中,它是如此沉重。该代码绝对不会有虫子的是不存在的代码-那里Python的原则是,应该是一个理想的执行任务明显的方式(C具有相同的原则的“C的精神”一节中ISO标准,顺便说一句)。
这并不意味着我们走的路,禁止的事情了(例如,混入以及一些应用类装饰之间近乎等价),但绝对不意味着我们不喜欢随身携带的编译器和代码/或冗余存在的运行时仅用于支持多种等效方法来执行完全相同的任务。
进一步的编辑:实际上,还有一种更好的方法可以为许多类提供比较和散列,包括问题中的那个-一种__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__
直接需要特殊的方法解决。
回答 1
为了简化这种情况,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
这是通过覆盖PEP 207 -丰富的比较
另外,__cmp__
在python 3.0中消失了。(请注意,它不在http://docs.python.org/3.0/reference/datamodel.html上,但是在http://docs.python.org/2.7/reference/datamodel.html上)
回答 3
(已于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
回答 4
受Alex Martelli的ComparableMixin
&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