Comparison Methods

by John | November 25, 2020

 

What are Comparisons in the Context of Object Orientated Programming?

 

We can compare objects using equality, greater than, greater than or equal to, less than, less than or equal to. Consider the following refresher on comparison between objects in Python you may already be familiar with:

 

1 > 2
#False
'a' < 'b'
# True
2 >= 4/2
# True
13.50 <= 14
#True
1 == 1.0
# True
'a' == 'f'
# False

 

We may want to use comparison operators between instances of objects. There are a number of built in methods Python provides for implementing this functionality. Notice that comparison methods returns either True or False. 

 

In order to demonstrate the comparison functionality let's create an example Circle class. The class will take a radius parameter and a computed property of area.

 

from math import pi

class Circle:
    def __init__(self, radius):
        self.radius = radius
        self.area = Circle.calculate_area(radius)
        
    @staticmethod
    def calculate_area(radius):
        return pi * pow(radius, 2)
    

 

 

__eq__ Method for Equality Comparison 

We can check that two items are equal using the __eq__ method within the class. For our example class we will declare two circles as being equal if they have the same area. 

from math import pi

class Circle:
    def __init__(self, radius):
        self.radius = radius
        self.area = Circle.calculate_area(radius)
        
    @staticmethod
    def calculate_area(radius):
        return pi * pow(radius, 2)
    
    
    def __eq__(self, another_circle):
        if not isinstance(another_circle, Circle):
            raise TypeError('Can only compare two Circles')   
        if self.area == another_circle.area:
            return True
        else:
            return False
    
    
c1 = Circle(10)
c2 = Circle(20)
c3 = Circle(20)

c1 == c2
#False
c2 == c3  
# True    
c1 != c2
# True

 

We can also use the not equal to operator !=.  However, it is worth noting that even if two objects are equal they aren't  necessarily the same object. This may be useful for understanding the difference between the is keyword and ==.

 

c2 = Circle(20)

c3 = Circle(20)

c2 is c3
# False

 

__lt__ & __le__ Methods

Let's say we wanted to compare whether a circle is less than another circle. We can try to compare c1 and c2 which we have created above and inspect the result. 

c1 = Circle(10)
c2 = Circle(20)
c1 < c2

 

When running the line of code above, we expect the the < operator to return True. However, we get the following error message.

TypeError: '<' not supported between instances of 'Circle' and 'Circle'

 

To implement this functionality we can use the __lt__ method as follows:

 

class Circle:
    def __init__(self, radius):
        self.radius = radius
        self.area = Circle.calculate_area(radius)
        
    @staticmethod
    def calculate_area(radius):
        return pi * pow(radius, 2)
    
    
    def __eq__(self, another_circle):
        if not isinstance(another_circle, Circle):
            raise TypeError('Can only compare two Circles')   
        if self.area == another_circle.area:
            return True
        else:
            return False
        
    def __lt__(self, another_circle):
        if not isinstance(another_circle, Circle):
            raise TypeError('Can only use less than comparison on two Circle objects')
        if self.area < another_circle.area:
            return True
        else:
            return False

   
c1 = Circle(10)

c2 = Circle(20)

c1 < c2
# returns True

 

So now we can use the < operator between our circle objects. However, if we try to use the <= operator between two circles like the line below:

 

c1 = Circle(10)
c2 = Circle(20)

c1 < c2
#returns True

c1 <= c2

 

Again the c1 <= c2 line above will return the following exception:

 

TypeError: '<=' not supported between instances of 'Circle' and 'Circle'

 

If we want to be able to use the <= operator between instances of the example class we need to implement the __le__ method within the class. 

 

class Circle:
    def __init__(self, radius):
        self.radius = radius
        self.area = Circle.calculate_area(radius)
        
    @staticmethod
    def calculate_area(radius):
        return pi * pow(radius, 2)
    
    
    def __eq__(self, another_circle):
        if not isinstance(another_circle, Circle):
            raise TypeError('Can only compare two Circles')   
        if self.area == another_circle.area:
            return True
        else:
            return False
        
    def __lt__(self, another_circle):
        if not isinstance(another_circle, Circle):
            raise TypeError('Can only use less than comparison on two Circle objects')
        if self.area < another_circle.area:
            return True
        else:
            return False
        
    def __le__(self, another_circle):
        if not isinstance(another_circle, Circle):
            raise TypeError('Can only use <= operator on two Circle objects')
        if self.area == another_circle.area or self.area < another_circle.area:
            return True
        else:
            return False



c1 = Circle(10)
c2 = Circle(20)

c1 < c2
#returns True

c1 <= c2
# returns True

 

What's really cool is that now that we have implemented the __lt__ & __le__ methods we don't have to implement any new methods for the greater (>) / greater than equal (>=). If we were to call the > operator as we do below Python still gives us the expected answer.

 

c1 = Circle(10)

c2 = Circle(20)


c1 > c2 
# returns False
c2 > c1
# returns True
c2 >= c1
#returns True

 

This is possible because if Python can't find a __gt__ or __ge__ method it will just flip the sign on the __lt__ / __le__ method to give us the desired result. However, in the interest of completeness we will give another example using the __gt__ and __ge__ methods below.

 

 

For the next section let's take a new example class. We will use a DistanceEquator class, which takes a name and latitude parameter in the constructor. To calculate an approximate distance from the equator in miles we can multiply the latitude by 69 in accordance with this interesting article by sciencing.com. The latitude values were calculated using this website

 

class DistanceEquator:
    #https://sciencing.com/convert-latitude-longtitude-feet-2724.html
    def __init__(self, place_name, latitude):
        self.place_name = place_name
        self.latitude = latitude
        self.miles = self.latitude * 69


London = DistanceEquator('London', 51.509865) 

Paris = DistanceEquator('Paris', 48.864716)

Jakarta = DistanceEquator('Jakarta', -6.200000)

Tokyo = DistanceEquator('Tokyo', 35.652832)

 

 

__gt__ and __ge__ Methods

Using our DistanceEquator class defined above, we will define a place as being greater than another if it is further away from the equator. This will be very similar to the __le__ & __lt__ methods we used in the Circle class.

Notice in the comparison methods below we have used abs(self.miles) as opposed to just self.miles, to understand this look at the distance from the equator for Jakarta. If a large negative number occurred we would get unexpected behavior, and since for this example, we are interested in the absolute distance from the equator, it is necessary to take the absolute value. 

 

class DistanceEquator:
    #https://sciencing.com/convert-latitude-longtitude-feet-2724.html
    def __init__(self, place_name, latitude):
        self.place_name = place_name
        self.latitude = latitude
        self.miles = self.latitude * 69
    
    def __eq__(self, other_place):
        if not isinstance(other_place, DistanceEquator):
            raise TypeError('Cannot use == comparison between these objects')
        if abs(self.miles) == abs(other_place.miles):
            return True
        else:
            return False
    
    def __gt__(self, other_place):
        if not isinstance(other_place, DistanceEquator):
            raise TypeError('Cannot use > operator between these objects')
            
        if abs(self.miles) > abs(other_place.miles):
            return True
        else:
            return False
        
    def __ge__(self, other_place):
         if not isinstance(other_place, DistanceEquator):
            raise TypeError('Cannot use >= operator between these objects')
         if abs(self.miles) > abs(other_place.miles) or abs(self.miles) == abs(other_place.miles):
            return True
         else:
            return False


London = DistanceEquator('London', 51.509865) 
Paris = DistanceEquator('Paris', 48.864716)
Jakarta = DistanceEquator('Jakarta', -6.200000)
Tokyo = DistanceEquator('Tokyo', 35.652832)

London >= Paris
# returns True
Tokyo > Jakarta
# returns True
Jakarta > London
#returns False        

 

Summary

- To compare two objects we need to implement the built in __comparison__ operators. 

 

- If we implement the < and <= methods or the > or >= or equal methods we get the others for free, since Python just flips the symbols if it can't find the method we called. 

 

 


Join the discussion

Share this post with your friends!