24

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

4

5 回答 5

27

为了使类具有可比性,您只需要__lt__使用functools.total_ordering. 如果可能,您还应该提供一种__eq__方法。这提供了其余的比较运算符,因此您不必自己编写它们中的任何一个。

于 2011-08-02T04:48:02.983 回答
14

对于一整套比较函数,我使用了以下 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 配方的原因是这个错误。它在 Python 3.4 中已修复,但通常您还需要支持较旧的 Python 版本。

于 2011-08-02T14:08:17.063 回答
0

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

__eq__, __gt__, __ge__, __lt__, __le__

正如agf所说,我错过了:

__ne__
于 2011-08-02T04:46:21.307 回答
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(),您可能希望让它引发异常而不是返回 none。

于 2011-08-02T05:08:25.010 回答
0

我只是想到了一种非常骇人听闻的方法。这与您最初尝试做的精神相同。它不需要向类对象添加任何函数;它适用于任何课程。

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)返回相同的值,您就知道将获得哪一个:具有最高索引的对象,即列表中最新的对象。如果你想要列表中最早的那个,你可以用一个键函数来做到这一点。

于 2011-08-02T05:41:14.177 回答