Python Rich ComparisonDeath to Boilerplate - A Mixin Class
![]()
Python and Rich ComparisonPython allows you to implement comparison for custom objects by operator overloading. You can either implement __cmp__, or all the rich comparison methods. For example, if you want your custom objects to be only comparable for equality and inequality with other objects, then you can provide the __eq__ and __ne__ methods. But you have to implement both. I think that every time I have implemented __eq__, the implementation of __ne__ has always amounted to nothing more than return not self.__eq__(other). Even if it doesn't work for 100% of the cases it would make a damn fine fallback... In practise, all of the rich comparison methods can be deduced from just implementing __eq__ and __lt__ and sensible fallbacks. Interestingly, IronPython has it a little better. You still have to provide both __eq__ and __ne__, but you only need to provide __lt__ or __gt__ as well; and then all comparisons give the right answer. Take the following class: class Comparer(object): def __init__(self, value): self.value = value def __eq__(self, other): if hasattr(other, 'value'): return self.value == other.value return self.value == other def __ne__(self, other): return not self.__eq__(other) def __lt__(self, other): if hasattr(other, 'value'): return self.value < other.value return self.value < other With CPython, it gives the right answer for equality / inequality and less than tests. For the other tests, it gives the wrong answers: >>> c = Comparer(6) >>> c == 6 True >>> c != 5 True >>> c < 7 True >>> c <= 6 False >>> c > 7 True >>> The answers to the last two comparisons should be True and False. With IronPython, we get the following: >>> c = Comparer(6) >>> c == 6 True >>> c != 5 True >>> c < 7 True >>> c <= 6 True >>> c > 7 False >>> Under the hood, .NET uses the comparison methods we provide (you can verify this by putting prints in to see which methods are called). To avoid the boilerplate, here's a RichComparisonMixin class. If you only want equality and inequality, then you only need to provide __eq__ in subclasses. For all the comparison methods, you only need to provide __eq__ and __lt__: class RichComparisonMixin(object): def __eq__(self, other): raise NotImplementedError("Equality not implemented") def __lt__(self, other): raise NotImplementedError("Less than not implemented") def __ne__(self, other): return not self.__eq__(other) def __gt__(self, other): return not (self.__lt__(other) or self.__eq__(other)) def __le__(self, other): return self.__eq__(other) or self.__lt__(other) def __ge__(self, other): return self.__eq__(other) or self.__gt__(other) To verify that it works, here are the tests. This includes an example class, RichComparer that subclasses RichComparisonMixin. All the rich comparison methods work, even though it only implements __eq__ and __lt__ : from unittest import main, TestCase from richcomparisonmixin import RichComparisonMixin class RichComparer(RichComparisonMixin): def __init__(self, value): self.value = value def __eq__(self, other): if not hasattr(other, 'value'): return self.value == other return self.value == other.value def __lt__(self, other): if not hasattr(other, 'value'): return self.value < other return self.value < other.value class RichComparisonMixinTest(TestCase): def setUp(self): TestCase.setUp(self) self.comp = RichComparer(6) def testDefaultComparison(self): self.assertRaises(NotImplementedError, lambda: RichComparisonMixin() == 3) self.assertRaises(NotImplementedError, lambda: RichComparisonMixin() != 3) self.assertRaises(NotImplementedError, lambda: RichComparisonMixin() < 3) self.assertRaises(NotImplementedError, lambda: RichComparisonMixin() > 3) self.assertRaises(NotImplementedError, lambda: RichComparisonMixin() <= 3) self.assertRaises(NotImplementedError, lambda: RichComparisonMixin() >= 3) def testEquality(self): self.assertTrue(self.comp == 6) self.assertTrue(self.comp == RichComparer(6)) self.assertFalse(self.comp == 7) self.assertFalse(self.comp == RichComparer(7)) def testInEquality(self): self.assertFalse(self.comp != 6) self.assertFalse(self.comp != RichComparer(6)) self.assertTrue(self.comp != 7) self.assertTrue(self.comp != RichComparer(7)) def testLessThan(self): self.assertTrue(self.comp < 7) self.assertTrue(self.comp < RichComparer(7)) self.assertFalse(self.comp < 5) self.assertFalse(self.comp < RichComparer(5)) self.assertFalse(self.comp < 6) self.assertFalse(self.comp < RichComparer(6)) def testGreaterThan(self): self.assertTrue(self.comp > 5) self.assertTrue(self.comp > RichComparer(5)) self.assertFalse(self.comp > 7) self.assertFalse(self.comp > RichComparer(7)) self.assertFalse(self.comp > 6) self.assertFalse(self.comp > RichComparer(6)) def testLessThanEqual(self): self.assertTrue(self.comp <= 7) self.assertTrue(self.comp <= RichComparer(7)) self.assertTrue(self.comp <= 6) self.assertTrue(self.comp <= RichComparer(6)) self.assertFalse(self.comp <= 5) self.assertFalse(self.comp <= RichComparer(5)) def testGreaterThanEqual(self): self.assertTrue(self.comp >= 5) self.assertTrue(self.comp >= RichComparer(5)) self.assertTrue(self.comp >= 6) self.assertTrue(self.comp >= RichComparer(6)) self.assertFalse(self.comp >= 7) self.assertFalse(self.comp >= RichComparer(7)) if __name__ == '__main__': main() You can download the mixin class and the tests from the recipebook. For buying techie books, science fiction, computer hardware or the latest gadgets: visit The Voidspace Amazon Store. If you're looking for a new techie job, try the Voidspace Tech Job Board. This is part of the Hidden Network of technology and programming jobs.
Last edited Fri Feb 15 13:42:08 2008. Counter... |
|
|
Blogads
Follow me on: Tech Jobs |