Делаем определяемый пользователем класс Python сортируемым, хешируемым

83

Какие методы необходимо переопределить / реализовать при создании сортируемых и / или хешируемых пользовательских классов в Python?

Какие подводные камни следует остерегаться?

Я ввожу dir({})свой интерпретатор, чтобы получить список методов для встроенных dicts. Из них, я полагаю, мне нужно реализовать какое-то подмножество

['__cmp__', '__eq__', '__ge__', '__gt__', '__hash__', '__le__', '__lt__', '__ne__']

Есть ли разница в том, какие методы должны быть реализованы для Python3, а не для Python2?

Мэтт Фенвик
источник
3
Хорошее обсуждение здесь: stackoverflow.com/q/1061283/641766 . Разница между Python 2.x и 3.x в том, что __cmp__была удалена.
zeekay

Ответы:

90

Я почти опубликовал это как комментарий к другим ответам, но это действительно ответ сам по себе.

Чтобы ваши предметы можно было сортировать, их нужно всего лишь реализовать __lt__. Это единственный метод, используемый встроенной сортировкой.

Другие сравнения functools.total_orderingнеобходимы только в том случае, если вы действительно хотите использовать операторы сравнения с вашим классом.

Чтобы сделать ваши элементы хешируемыми, вы реализуете, __hash__как отметили другие. Вы также должны реализовать __eq__совместимым способом - элементы, которые эквивалентны, должны иметь одинаковый хэш.

agf
источник
Итак, плохая реализация __lt__может привести к непредсказуемой сортировке Python? (например, if x .__ lt __ (y) и y .__ lt __ (x))
Мэтт Фенвик
3
Я не знаю насчет «непредсказуемо», это будет согласованно, если будет подаваться один и тот же ввод, но другой порядок ввода может привести к тому, что разные элементы будут в другом порядке. Да, если вы неправильно реализуете сравнение, используемое для сортировки, Python будет выполнять сортировку неправильно. Я бы порекомендовал __key__функцию, которая превращает экземпляр в кортеж, а затем просто используйте его как __lt__( self.__key__() < other.__key__()), так и __hash__( hash(self.__key__())).
agf
21

Между Python 2 и 3 нет никакой разницы.

Для сортировки:

Вы должны определить методы сравнения. Это позволяет сортировать ваши предметы. Как правило, вы не должны отдавать предпочтение __cmp__().

Обычно я использую декоратор functools.total_ordering.

functools.total_ordering (cls) Для класса, определяющего один или несколько богатых методов упорядочивания сравнения, этот декоратор класса предоставляет все остальное. Это упрощает работу по указанию всех возможных операций расширенного сравнения:

Класс должен определить один из __lt__(), __le__(), __gt__()или __ge__(). Кроме того, класс должен предоставить __eq__()метод.

Вы должны быть осторожны, чтобы ваши методы сравнения не имели побочных эффектов. (изменить любое из значений объекта)

Для хеширования:

Вы должны реализовать __hash__()метод. Думаю, лучший способ - вернуться hash(repr(self)), чтобы ваш хеш был уникальным.

утдемир
источник
Пример functools.total_orderingиз документации см. Здесь .
Евгений Сергеев
3

Есть несколько способов пометить объект как объект сортировки. Первый - богатое сравнение, определяемое набором функций:

object.__lt__(self, other)
object.__le__(self, other)
object.__eq__(self, other)
object.__ne__(self, other)
object.__gt__(self, other)
object.__ge__(self, other)

Также можно определить только одну функцию:

object.__cmp__(self, other)

И последнее должно быть определено, если вы хотите определить пользовательскую __hash__функцию. См. Док .

Роман Боднарчук
источник
6
В Python 3 «[...] __cmp__()специальный метод больше не поддерживается», см. Соответствующий раздел здесь .
Евгений Сергеев
-3

__lt__(self,other)Метод реализации - это ответ, позволяющий упорядочить ваш класс.
Его можно использовать не только для встроенного метода sorted(iterable), но и для очереди приоритетов через heapqмодуль.

Кроме того, мне не нравится дизайн Python, поэтому многие '__ge__', '__gt__', '__le__', '__lt__', '__ne__'методы вообще не интуитивно понятны !
Напротив, Java Interface Comparable<T> (см. Java doc ) возвращает отрицательное целое число, ноль или положительное целое число, поскольку этот объект меньше, равен или больше указанного объекта, что является прямым и понятным !

ичуду
источник
9
По большей части ваш ответ отражает ваше мнение (которое не должно быть частью ответа).
skyking
@skyking ... хотя я не согласен с мнениями в этом конкретном ответе, я считаю, что мнение (с исследованными и полезными данными, относящимися к вопросу) очень ценно. Ошибка в этом мнении не подкрепляет мнение соответствующими данными. Но понимание предпочтений помогает людям принимать решения и поэтому очень полезно. Здесь ответ неверен, потому что нет полезного и действенного кода на Python, чтобы проиллюстрировать питонический способ кодирования на основе предпочтений автора.
Эндрю