使用__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__
一样简单地实现比较?
5 个回答
这部分内容可以在PEP 207 - 丰富比较中找到。
另外,__cmp__
这个东西在Python 3.0中被去掉了。(注意,在http://docs.python.org/3.0/reference/datamodel.html上找不到它,但在http://docs.python.org/2.7/reference/datamodel.html上是有的。)
为了简化这个情况,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__
这样的方式来实现功能其实很简单,可以通过混入类(mixin class)、元类(metaclass)或者类装饰器(class decorator)来做到,具体看你喜欢哪种方式。
举个例子:
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__
,那么它应该直接定义这些方法,这样就不会使用混入类里的版本(例如,dict
就是这样的情况)——实际上 __ne__
可能会被定义成这样来方便实现:
def __ne__(self, other):
return not self == other
不过在上面的代码中,我想保持只用 <
的对称性,嘿嘿。至于为什么 __cmp__
要被去掉,因为我们已经有了 __lt__
和其他相关的方法,为什么还要保留另一种不同的方式来做同样的事情呢?这在每个 Python 运行环境中都是多余的负担(比如 Classic、Jython、IronPython、PyPy 等等)。那些绝对不会出错的代码就是那些不存在的代码——这也是 Python 的一个原则:理想情况下,应该有一种明显的方法来完成一项任务(顺便提一下,C 语言在 ISO 标准的“C 的精神”部分也有类似的原则)。
这并不是说我们要故意禁止某些东西(例如,混入类和类装饰器在某些用法上几乎是等价的),但这确实意味着我们不喜欢在编译器和/或运行时中携带那些冗余的代码,只是为了支持多种等效的方法来完成同样的任务。
进一步补充:其实还有一种更好的方法可以为许多类提供比较和哈希功能,包括问题中的类——那就是 __key__
方法,正如我在对问题的评论中提到的。因为我还没来得及为它写 PEP,所以如果你想用的话,目前只能通过混入类来实现:
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__
这个特殊方法正好满足了这个需求。