Search code examples
pythonpython-3.xinheritancefunctools

Total_ordering and class inheritance


From what I understand, the total_ordering decorator from functools is not expected to work nicely with classes inherited from an ordered class: it doesn't try to define the comparison functions because they are already defined.

See this example:

from functools import total_ordering
from collections import namedtuple

Test = namedtuple('Test',['a','b'])

@total_ordering
class TestOrd(Test):
    def __lt__(self,other):
        return self.b < other.b or self.b == other.b and self.a < other.a

x = TestOrd(a=1,b=2)
y = TestOrd(a=2,b=1)
print(x < y)   # Expected: False
print(x <= y)  #           False
print(x > y)   #           True
print(x >= y)  #           True
print(y < x)   #           True
print(y <= x)  #           True
print(y > x)   #           False
print(y >= x)  #           False

Of all the tests, only the ones involving the < operator give the expected result.

I can get the > ones to work as well by adding __gt__ = lambda *_ : NotImplemented to the class definition. On the other hand, if I add similar definitions for __le__ or __ge__, the corresponding tests fail with (for __le__):

TypeError: unorderable types: TestOrd() <= TestOrd()

which leads me to believe that this is not the proper way to address the problem.

Hence the question: is there a proper way to reorder a class with total_ordering?

(Yes, I know that doing total_ordering's job by hand is trivial, and I know that for this example, defining an unordered namedtuple is trivial too.)


Solution

  • For your example, you could solve the problem by introducing an additional base class that does not directly inherit from Test:

    Test = namedtuple('Test',['a','b'])
    
    @total_ordering
    class TestOrdBase:
        def __lt__(self ,other):
            return self.b < other.b or self.b == other.b and self.a < other.a
    
    class TestOrd(TestOrdBase, Test):
        pass
    

    The order of the base classes for TestOrd is important, TestOrdBase must come before Test.