Elegant compare

J

Jason Friedman

class my_class:
def __init__(self, attr1, attr2):
self.attr1 = attr1 #string
self.attr2 = attr2 #string
def __lt__(self, other):
if self.attr1 < other.attr1:
return True
else:
return self.attr2 < other.attr2

I will run into problems if attr1 or attr2 is None, and they
legitimately can be.

I know I can check for attr1 or attr2 or both being None and react
accordingly, but my real class has ten attributes and that approach
will be long. What are my alternatives?
 
S

Steven D'Aprano

class my_class:
def __init__(self, attr1, attr2):
self.attr1 = attr1 #string
self.attr2 = attr2 #string
def __lt__(self, other):
if self.attr1 < other.attr1:
return True
else:
return self.attr2 < other.attr2

I will run into problems if attr1 or attr2 is None, and they
legitimately can be.

I know I can check for attr1 or attr2 or both being None and react
accordingly, but my real class has ten attributes and that approach will
be long. What are my alternatives?

This is a hard question to answer, because your code snippet isn't
clearly extensible to the case where you have ten attributes. What's the
rule for combining them? If instance A has five attributes less than
those of instance B, and five attributes greater than those of instance
B, which wins?

But if I had to guess an approach, I'd start with a helper function (or
method) that compares two raw values:

def compare(a, b):
"""Return -ve for less than, 0 for equal, +ve for greater than."""
if a is None:
return 0 if b is None else -1
if b is None:
return 1
return (b < a) - (a < b)


Now, in your class, you can use this helper function to check each
attribute in turn. Assuming that if an attribute is equal, you move on to
check the next one:

class MyClass:
def _compare(self, other):
for attr in 'attr1 attr2 attr3 attr4'.split():
a, b = getattr(self, attr), getattr(other, attr)
triflag = compare(a, b)
if triflag:
return triflag
return 0
def __lt__(self, other):
if not isinstance(other, MyClass):
return NotImplemented
return self._compare(other) < 0
def __eq__(self, other):
if not isinstance(other, MyClass):
return NotImplemented
return not self._compare(other)
def __ne__(self, other):
if not isinstance(other, MyClass):
return NotImplemented
return bool(self._compare(other))

and so on. You can save a certain amount of repetition (by my count, six
lines of code) by pulling out the "if not isinstance" check into a
decorator, but since the decorator is likely to be about six lines long,
I wouldn't bother :)
 
J

Jason Friedman

This is a hard question to answer, because your code snippet isn't
clearly extensible to the case where you have ten attributes. What's the
rule for combining them? If instance A has five attributes less than
those of instance B, and five attributes greater than those of instance
B, which wins?

Yes, my code snippet was too short, I should have said:

class my_class:
def __init__(self, attr1, attr2, attr3):
self.attr1 = attr1 #string
self.attr2 = attr2 #string
self.attr3 = attr3 #string
def __lt__(self, other):
if self.attr1 < other.attr1:
return True
elif self.attr2 < other.attr2:
return True
else:
return self.attr3 < other.attr3

Chris's answer is actually perfectly adequate for my needs.
Thank you Steve and Chris.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,780
Messages
2,569,611
Members
45,280
Latest member
BGBBrock56

Latest Threads

Top