使用__lt__代替__cmp__

18 浏览
0 Comments

使用__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__方法一样简单?

admin 更改状态以发布 2023年5月20日
0
0 Comments

为了简化这个情况,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()))

0
0 Comments

嗯,用一个组合类(或元类,或类装饰器,如果你喜欢)以__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__特殊方法直接解决了这种需求。

0