444,089 Members | 2,418 Online
Need help? Post your question and get tips & solutions from a community of 444,089 IT Pros & Developers. It's quick & easy.

# Boilerplate in rich comparison methods

 P: n/a I'm writing a class that implements rich comparisons, and I find myself writing a lot of very similar code. If the calculation is short and simple, I do something like this: class Parrot: def __eq__(self, other): return self.plumage() == other.plumage() def __ne__(self, other): return self.plumage() != other.plumage() def __lt__(self, other): return self.plumage() < other.plumage() def __gt__(self, other): return self.plumage() other.plumage() def __le__(self, other): return self.plumage() <= other.plumage() def __ge__(self, other): return self.plumage() >= other.plumage() If the comparison requires a lot of work, I'll do something like this: class Aardvark: def __le__(self, other): return lots_of_work(self, other) def __gt__(self, other): return not self <= other # etc. But I can't help feeling that there is a better way. What do others do? -- Steven. Jan 13 '07 #1
11 Replies

 P: n/a Steven D'Aprano = other.plumage() If it's that uniform I think you can just use __cmp__: class Parrot: def __cmp__(self, other): return cmp(self.plumage(), other.plumage()) Did I miss something? The idea of rich comparison is that the different relations aren't so similar to each other, e.g. some kind of partial ordering. If the comparison requires a lot of work, I'll do something like this: class Aardvark: def __le__(self, other): return lots_of_work(self, other) def __gt__(self, other): return not self <= other # etc. But I can't help feeling that there is a better way. What do others do? Same as above. Jan 13 '07 #2

 P: n/a Steven D'Aprano wrote: I'm writing a class that implements rich comparisons, and I find myself writing a lot of very similar code. If the calculation is short and simple, I do something like this: class Parrot: def __eq__(self, other): return self.plumage() == other.plumage() def __ne__(self, other): return self.plumage() != other.plumage() def __lt__(self, other): return self.plumage() < other.plumage() def __gt__(self, other): return self.plumage() other.plumage() def __le__(self, other): return self.plumage() <= other.plumage() def __ge__(self, other): return self.plumage() >= other.plumage() If the comparison requires a lot of work, I'll do something like this: class Aardvark: def __le__(self, other): return lots_of_work(self, other) def __gt__(self, other): return not self <= other # etc. But I can't help feeling that there is a better way. What do others do? Once upon a time I had written a metaclass to generate the boilerate, filling in the gaps. It doesn't impose any constraints on which comparisons you must implement, e.g you may implement __le__ and __eq__, or __gt__ and __ne__, etc. Season to taste. http://rafb.net/p/mpvsIQ37.nln.html George Jan 13 '07 #3

 P: n/a "George Sakkis" I'm writing a class that implements rich comparisons, and I find myselfwriting a lot of very similar code. If the calculation is short andsimple, I do something like this: class Parrot: def __eq__(self, other): return self.plumage() == other.plumage() >If the comparison requires a lot of work, I'll do something like this:class Aardvark: def __le__(self, other): return lots_of_work(self, other) def __gt__(self, other): return not self <= other Once upon a time I had written a metaclass to generate the boilerate, filling in the gaps. It doesn't impose any constraints on which comparisons you must implement, e.g you may implement __le__ and __eq__, or __gt__ and __ne__, etc. Season to taste. http://rafb.net/p/mpvsIQ37.nln.html George Just a side note on writing these comparison operators. I remember when learning Java that this was really the first time I spent so much time reading about testing-for-identity vs. testing-for-equality. The Java conventional practice at the time was to begin each test-for-equality method by testing to see if an object were being compared against itself, and if so, cut to the chase and return True (and the converse for an inequality comparison). The idea behind this was that there were ostensibly many times in code where an object was being compared against itself (not so much in an explicit "if x==x" but in implicit tests such as list searching and filtering), and this upfront test-for-identity, being very fast, could short-circuit an otherwise needless comparison. In Python, this would look like: class Parrot: def __eq__(self, other): return self is other or self.plumage() == other.plumage() def __ne__(self, other): return self is not other and self.plumage() != other.plumage() def __lt__(self, other): return self is not other and self.plumage() < other.plumage() def __gt__(self, other): return self is not other and self.plumage() other.plumage() def __le__(self, other): return self is not other and self.plumage() <= other.plumage() def __ge__(self, other): return self is not other and self.plumage() >= other.plumage() and George's metaclass would have similar changes. On the other hand, I haven't seen this idiom in any Python code that I've read, and I wonder if this was just a coding fad of the time. Still, in cases such as Steven's Aardark class, it might be worth bypassing something that calls lots_of_work if you tested first to see if self is not other. -- Paul Jan 13 '07 #4

 P: n/a "Paul McGuire" = other.plumage() and George's metaclass would have similar changes. On the other hand, I haven't seen this idiom in any Python code that I've read, and I wonder if this was just a coding fad of the time. It is a perfectly reasonable short-cut for those types where you know an object is equal to itself, but that isn't always the case. e.g. floating point NaN values are not equal to themselves, and a list of numbers might contain a NaN which would mean the list wouldn't be equal to itself. Also note that for the __le__, __ge__ cases you got the shortcut test the wrong way round. Jan 13 '07 #5

 P: n/a On Jan 13, 12:52 am, Steven D'Aprano = other.plumage() If the comparison requires a lot of work, I'll do something like this: class Aardvark: def __le__(self, other): return lots_of_work(self, other) def __gt__(self, other): return not self <= other # etc. But I can't help feeling that there is a better way. What do others do? Typically, I write only two kinds of classes that use comparion operators: (1) ones that can get by with __cmp__ and (2) ones that define __eq__ and __ne__ without any of the other four. But for your case, I'd say you're doing it the right way. If you define a lot of classes like Parrot, you might want to try moving the six operators to a common base class: class Comparable: """ Abstract base class for classes using rich comparisons. Objects are compared using their cmp_key() method. """ def __eq__(self, other): return (self is other) or (self.cmp_key() == other.cmp_key()) def __ne__(self, other): return (self is not other) and (self.cmp_key() != other.cmp_key()) def __lt__(self, other): return self.cmp_key() < other.cmp_key() def __le__(self, other): return self.cmp_key() <= other.cmp_key() def __gt__(self, other): return self.cmp_key() other.cmp_key() def __ge__(self, other): return self.cmp_key() >= other.cmp_key() def cmp_key(self): """Overriden by derived classes to define a comparison key.""" raise NotImplementedError() class Parrot(Comparable): def cmp_key(self): return self.plumage() # ... Jan 13 '07 #6

 P: n/a On Sat, 13 Jan 2007 10:04:17 -0600, Paul McGuire wrote: Just a side note on writing these comparison operators. I remember when learning Java that this was really the first time I spent so much time reading about testing-for-identity vs. testing-for-equality. The Java conventional practice at the time was to begin each test-for-equality method by testing to see if an object were being compared against itself, and if so, cut to the chase and return True (and the converse for an inequality comparison). The idea behind this was that there were ostensibly many times in code where an object was being compared against itself (not so much in an explicit "if x==x" but in implicit tests such as list searching and filtering), and this upfront test-for-identity, being very fast, could short-circuit an otherwise needless comparison. In Python, this would look like: class Parrot: def __eq__(self, other): return self is other or self.plumage() == other.plumage() [snip] Surely this is only worth doing if the comparison is expensive? Testing beats intuition, so let's find out... class Compare: def __init__(self, x): self.x = x def __eq__(self, other): return self.x == other.x class CompareWithIdentity: def __init__(self, x): self.x = x def __eq__(self, other): return self is other or self.x == other.x Here's the timing results without the identity test: >>import timeitx = Compare(1); y = Compare(1)timeit.Timer("x = x", "from __main__ import x,y").repeat() [0.20771503448486328, 0.16396403312683105, 0.16507196426391602] >>timeit.Timer("x = y", "from __main__ import x,y").repeat() [0.20918107032775879, 0.16187810897827148, 0.16351795196533203] And with the identity test: >>x = CompareWithIdentity(1); y = CompareWithIdentity(1)timeit.Timer("x = x", "from __main__ import x,y").repeat() [0.20761799812316895, 0.16907095909118652, 0.16420602798461914] >>timeit.Timer("x = y", "from __main__ import x,y").repeat() [0.2090909481048584, 0.1968839168548584, 0.16479206085205078] Anyone want to argue that this is a worthwhile optimization? :) On the other hand, I haven't seen this idiom in any Python code that I've read, and I wonder if this was just a coding fad of the time. Still, in cases such as Steven's Aardark class, it might be worth bypassing something that calls lots_of_work if you tested first to see if self is not other. The comparison itself would have to be quite expensive to make it worth the extra code. -- Steven. Jan 13 '07 #7

 P: n/a On Fri, 12 Jan 2007 23:28:06 -0800, Paul Rubin wrote: Steven D'Aprano class Parrot: def __eq__(self, other): return self.plumage() == other.plumage() def __ne__(self, other): return self.plumage() != other.plumage() def __lt__(self, other): return self.plumage() < other.plumage() [snip more boilerplate code] If it's that uniform I think you can just use __cmp__: Good point -- I had somehow picked up the mistaken idea that __cmp__ was depreciated in favour of rich comparisons. -- Steven. Jan 13 '07 #8

 P: n/a On 2007-01-13, Steven D'Aprano >>import timeitx = Compare(1); y = Compare(1)timeit.Timer("x = x", "from __main__ import x,y").repeat() [0.20771503448486328, 0.16396403312683105, 0.16507196426391602] >>>timeit.Timer("x = y", "from __main__ import x,y").repeat() [0.20918107032775879, 0.16187810897827148, 0.16351795196533203] And with the identity test: >>>x = CompareWithIdentity(1); y = CompareWithIdentity(1)timeit.Timer("x = x", "from __main__ import x,y").repeat() [0.20761799812316895, 0.16907095909118652, 0.16420602798461914] >>>timeit.Timer("x = y", "from __main__ import x,y").repeat() [0.2090909481048584, 0.1968839168548584, 0.16479206085205078] Anyone want to argue that this is a worthwhile optimization? :) Perhaps. But first test it with "==". -- Neil Cerutti Jan 13 '07 #9

 P: n/a On Sat, 13 Jan 2007 22:05:53 +0000, Neil Cerutti wrote: >Anyone want to argue that this is a worthwhile optimization? :) Perhaps. But first test it with "==". Oh the ignominy! That's what happens when I run code at 6am :( >>x = CompareWithIdentity(1); y = CompareWithIdentity(1)timeit.Timer("x == y", "from __main__ import x,y").repeat() [2.2971229553222656, 2.2821698188781738, 2.2767620086669922] >>timeit.Timer("x == x", "from __main__ import x,y").repeat() [1.6935880184173584, 1.6783449649810791, 1.6613109111785889] >>x = Compare(1); y = Compare(1)timeit.Timer("x == y", "from __main__ import x,y").repeat() [2.1717329025268555, 2.1361908912658691, 2.1338419914245605] So for this simple case, testing for identity is a factor of 1.3 faster when the objects are identical, and a factor of 1.1 slower if they aren't. That suggests that if about 33% of your comparisons match by identity, you'll break-even; any less than that, and the optimization is actually a pessimation. -- Steven. Jan 14 '07 #10

 P: n/a "Steven D'Aprano" If it's that uniform I think you can just use __cmp__: Good point -- I had somehow picked up the mistaken idea that __cmp__ was depreciated in favour of rich comparisons. If you inherit from a base class that implements rich comparisons, you have to override all those methods, else your __cmp__ won't be called at all. Sometimes it may be enough to implement __cmp__ and make all others call it. -- Gabriel Genellina Jan 14 '07 #11

 P: n/a On 2007-01-13, Steven D'Aprano = other.plumage() Well one thing you could do is write the following class: Comparators = SomeEnumWith("eq, ne, lt, gt, ge, le, ge") class GeneralComparator: def __eq__(self, other): return Comparators.eq in self.__compare__(self, other) def __ne__(self, other): return Comparators.ne in self.__compare__(self, other) def __lt__(self, other): return Comparators.lt in self.__compare__(self, other) def __le__(self, other): return Comparators.le in self.__compare__(self, other) def __gt__(self, other): return Comparators.gt in self.__compare__(self, other) def __ge__(self, other): return Comparators.ge in self.__compare__(self, other) Then write your Parrot class as follows: class Parrot (GeneralComparator): def __compare__(self, other): return a set which defines which comparisons should return true. -- Antoon Pardon Jan 15 '07 #12

### This discussion thread is closed

Replies have been disabled for this discussion.