Python 3中的可比较类

时间:2011-08-02 04:40:54

标签: python class python-3.x comparable

在Python 3中使类可比的标准方法是什么? (例如,通过id。)

5 个答案:

答案 0 :(得分:7)

sort仅需__lt__

functools.total_ordering(截至2.7 / 3.2)是一个提供所有比较运算符的装饰器,因此您不必自己编写所有这些运算符。

默认情况下,类是可清除的,并使用它们的id();我不确定你为什么要按id()订购课程,除非你只是希望订单稳定。

答案 1 :(得分:6)

对于一整套比较函数,我使用了以下mixin,你可以在模块中输入一个mixin.py。

class ComparableMixin(object):
    def _compare(self, other, method):
        try:
            return method(self._cmpkey(), other._cmpkey())
        except (AttributeError, TypeError):
            # _cmpkey not implemented, or return different type,
            # so I can't compare with "other".
            return NotImplemented

    def __lt__(self, other):
        return self._compare(other, lambda s, o: s < o)

    def __le__(self, other):
        return self._compare(other, lambda s, o: s <= o)

    def __eq__(self, other):
        return self._compare(other, lambda s, o: s == o)

    def __ge__(self, other):
        return self._compare(other, lambda s, o: s >= o)

    def __gt__(self, other):
        return self._compare(other, lambda s, o: s > o)

    def __ne__(self, other):
        return self._compare(other, lambda s, o: s != o)

要使用上面的mixin,您需要实现一个_cmpkey()方法,该方法返回可以比较的对象的键,类似于排序时使用的key()函数。实现可能如下所示:

>>> from .mixin import ComparableMixin

>>> class Orderable(ComparableMixin):
...
...     def __init__(self, firstname, lastname):
...         self.first = firstname
...         self.last = lastname
...
...     def _cmpkey(self):
...         return (self.last, self.first)
...
...     def __repr__(self):
...         return "%s %s" % (self.first, self.last)
...
>>> sorted([Orderable('Donald', 'Duck'), 
...         Orderable('Paul', 'Anka')])
[Paul Anka, Donald Duck]

我使用它而不是total_ordering配方的原因是this bug。它已在Python 3.4中修复,但通常也需要支持较旧的Python版本。

答案 2 :(得分:0)

不确定这是否完整,但您要定义:

__eq__, __gt__, __ge__, __lt__, __le__

正如agf所说,我失踪了:

__ne__

答案 3 :(得分:0)

你说你正试图这样做:

max((f(obj), obj) for obj in obj_list)[1]

你应该这样做:

max(f(obj) for obj in obj_list)

编辑:或者正如gnibbler所说:max(obj_list, key=f)

但是你告诉gnibbler你需要一个对象引用max对象。我认为这是最简单的:

def max_obj(obj_list, max_fn):
    if not obj_list:
        return None

    obj_max = obj_list[0]
    f_max = max_fn(obj)

    for obj in obj_list[1:]:
        if max_fn(obj) > f_max:
            obj_max = obj
    return obj_max

obj = max_obj(obj_list)

当然,如果您尝试查找空列表的max_obj(),您可能希望让它引发异常而不是返回任何异常。

答案 4 :(得分:0)

我只是想到了一种真正的hackish方式。这与您最初尝试的精神相同。它不需要向类对象添加任何函数;它适用于任何类。

max(((f(obj), obj) for obj in obj_list), key=lambda x: x[0])[1]

我真的不喜欢这样,所以这里有一些不那么简洁的东西:

def make_pair(f, obj):
    return (f(obj), obj)

def gen_pairs(f, obj_list):
    return (make_pair(f, obj) for obj in obj_list)

def item0(tup):
    return tup[0]

def max_obj(f, obj_list):
    pair = max(gen_pairs(f, obj_list), key=item0)
    return pair[1]

或者,如果obj_list始终是像列表一样的可索引对象,则可以使用此单行:

obj_list[max((f(obj), i) for i, obj in enumerate(obj_list))[1]]

这样做的好处是,如果有多个对象使f(obj)返回相同的值,您就知道将获得哪一个:索引最高的那个,即列表中的最新一个。如果你想要列表中最早的那个,你可以用一个关键功能来做到这一点。