Сопоставимые классы в Python 3 - PullRequest
8 голосов
/ 02 августа 2011

Каков стандартный способ сделать класс сопоставимым в Python 3? (Например, по идентификатору.)

Ответы [ 5 ]

7 голосов
/ 02 августа 2011

Для полного набора функций сравнения я использовал следующий 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)

Чтобы использовать вышеописанный миксин, вам нужно реализовать метод _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.

7 голосов
/ 02 августа 2011

sort нужно только __lt__.

functools.total_ordering (по состоянию на 2.7 / 3.2) - это декоратор, который предоставляет все операторы сравнения, поэтому вам не нужно писать их все самостоятельно.

По умолчанию классы являются хэшируемыми, и для этого используются их id(); Я не уверен, почему вы хотите упорядочить классы по их id(), если только вы не хотите, чтобы порядок был стабильным.

0 голосов
/ 02 августа 2011

Я просто подумал о действительно хакерском способе сделать это. Это в том же духе, что вы изначально пытались сделать. Это не требует добавления каких-либо функций к объекту класса; это работает для любого класса.

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) возвращает идентичное значение, вы знаете, какое из них вы получите: одно с наивысшим индексом, т.е. самое последнее в списке. Если вам нужен самый ранний в списке, вы можете сделать это с помощью ключевой функции.

0 голосов
/ 02 августа 2011

Вы сказали, что пытаетесь сделать это:

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 () пустого списка.

0 голосов
/ 02 августа 2011

Не уверен, что это завершено, но вы хотите определить:

__eq__, __gt__, __ge__, __lt__, __le__

Как сказал agf , я скучаю:

__ne__
...