функции total_ordering, похоже, ничего не делают с унаследованным классом

#python #oop #functools

Вопрос:

Я пытаюсь отсортировать список строк таким образом, чтобы использовать специальное сравнение. Я пытаюсь использовать functools.total_ordering , но я не уверен, правильно ли он заполняет неопределенные сравнения.

Два, которые я определяю ( > и==), работают так, как ожидалось, но > В частности, я печатаю все три и получаю это a > b и a < b . Как это возможно? Я бы подумал, что total_ordering будет просто определять < как not > and not == . Результат моего < теста-это то, что вы получили бы при регулярном сравнении str, что заставляет меня поверить, что total_ordering ничего не делает.

Возможно, проблема в том, что я наследую str, который уже __lt__ реализован? Если да, то есть ли решение этой проблемы?

 from functools import total_ordering

@total_ordering
class SortableStr(str):

    def __gt__(self, other):
        return self other > other self

    #Is this necessary? Or will default to inherited class?
    def __eq__(self, other):
        return str(self) == str(other)

def main():

    a = SortableStr("99")
    b = SortableStr("994")

    print(a > b)
    print(a == b)
    print(a < b)

if __name__ == "__main__":
    main()
 

выход:

 True
False
True
 

Комментарии:

1. Вы сказали, что считаете, что «логика[не] очень уместна для моего вопроса». Не могли бы вы все равно поделиться этим? Если вы объясните, какие результаты вы ожидаете увидеть, кто-то, возможно, сможет предложить альтернативное решение. Например, кем вы ожидаете "555" < "555999" стать? Или "555" > "555999" ?

2. Почему вы сравниваете next_dig цифру в той же строке?

3. Ах, просто прочитайте свой вопрос немного внимательнее. Вы говорите , что получаете ожидаемые значения True для a > b и False для a == b , но вы получаете неожиданный результат True для a < b .

4. Я упростил вопрос, мой новый код получает тот же результат. Для контекста я работаю над этой проблемой: leetcode.com/problems/largest-number/solution

5. @PaulFornia Множество встроенных модулей Python, включая сортировку, гарантированно используются только __lt__ для того , чтобы упростить и сделать более последовательным использование с ним различных классов. См. раздел Сортировка КАК#Всякая всячина . Это также верно в C , где большинство встроенных алгоритмов гарантированно используются operator< вместо того, чтобы требовать выполнения всех операций упорядочения.

Ответ №1:

Вы правы в том, что встроенные str операторы сравнения вмешиваются в ваш код. Из документов

Учитывая класс, определяющий один или несколько методов упорядочения богатых сравнений, этот декоратор класса предоставляет все остальное.

Таким образом, он предоставляет только те, которые еще не определены. В вашем случае тот факт, что некоторые из них определены в родительском классе, невозможно обнаружить total_ordering .

Теперь мы можем углубиться в исходный код и найти точную проверку

 roots = {op for op in _convert if getattr(cls, op, None) is not getattr(object, op, None)}
 

Таким образом, он проверяет, равны ли значения значениям, определенным в корневом объекте object . Мы можем это сделать

 @total_ordering
class SortableStr(str):

    __lt__ = object.__lt__
    __le__ = object.__le__
    __ge__ = object.__ge__

    def __gt__(self, other):
        return self other > other self

    #Is this necessary? Or will default to inherited class?
    def __eq__(self, other):
        return str(self) == str(other)
 

Теперь total_ordering увидим, что __lt__ , __le__ , и __ge__ равны «исходным» object значениям и перепишем их, по желанию.


Все это, как говорится, я бы сказал, что это плохое использование наследования. Вы нарушаете замену Лискова, по крайней мере, в том, что смешанные сравнения между str и SortableStr , мягко говоря, приведут к нелогичным результатам.

Моя более общая рекомендация состоит в том, чтобы отдать предпочтение композиции, а не наследованию, и вместо определения вещи, которая «является» специализированной строкой, рассмотрите возможность определения типа, который «содержит» строку и имеет специализированное поведение.

 @total_ordering
class SortableStr:

    def __init__(self, value):
        self.value = value

    def __gt__(self, other):
        return self.value   other.value > other.value   self.value

    def __eq__(self, other):
        return self.value == other.value
 

Там не требуется никакой магии. Теперь SortableStr("99") это допустимый объект, который не является строкой, но демонстрирует желаемое поведение.

Комментарии:

1. Просто мысль, но можно ли заставить это работать с а collections.UserString ? Я предполагаю, что нет, потому что в документах явно говорится, что он поддерживает «методы и операции» обычных строк, и я предполагаю, что эти магические методы относятся к этой категории.

2. Фантастика, спасибо! Итак, что касается моего вопроса Полу М. ниже о том, как «ООН» реализует метод, я думаю, что ответ заключается __lt__ = object.__lt__ в том, что это делает это эффективно. Интересный. Но определенно следует отметить, что наследование здесь не самый чистый подход.

Ответ №2:

Не уверен, правильно ли это , но, взглянув на документацию functools.total_ordering , я понял, что это бросается в глаза:

Учитывая класс, определяющий один или несколько методов упорядочения с расширенным сравнением, этот декоратор класса предоставляет все остальное.

Акцент мой. Ваш класс наследуется __lt__ от str , поэтому он не будет повторно реализован, total_ordering так как он не отсутствует. Это мое лучшее предположение.

Комментарии:

1. Хорошо, да, я думаю, что ты прав. Так есть ли какое-то решение? Есть ли способ «отменить» реализацию метода без его перезаписи? Если нет, то, я думаю functools.total_ordering , просто не работает в этой ситуации?

2. Похоже, @SilvioMayolo только что опубликовал более полный ответ, в котором пришел к тому же выводу. Взгляните!