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.)
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
.