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.