Object Oriented Programming: Comparing and inheritance
A summary from a course on datacamp.com:
Overloading equality
When comparing two objects of a custom class using ==
, Python by default compares just the object references, not the data contained in the objects. To override this behavior, the class can implement the special __eq__()
method, which accepts two arguments — the objects to be compared — and returns True
or False
. This method will be implicitly called when two objects are compared.
The BankAccount
class from the previous chapter is available for you in the script pane. It has one attribute, balance
, and a withdraw()
method. Two bank accounts with the same balance are not necessarily the same account, but a bank account usually has an account number, and two accounts with the same account number should be considered the same.
Try selecting the code in lines 1-7 and pressing the “Run code” button. Then try to create a few BankAccount
objects in the console and compare them.
- Modify the
__init__()
method to accept a new parameter –number
– and initialize a newnumber
attribute. - Define an
__eq__()
method that returnsTrue
if thenumber
attribute of two objects is equal. - Examine the print statements and the output in the console.
- The
__eq__()
method should accept two arguments, usually calledself
andother
. - When adding parameters to
__init__()
, remember that parameters without default values should be placed before parameters that have default values.
class BankAccount:
# MODIFY to initialize a number attribute
def __init__(self, number, balance=0):
self.balance = balance
self.number = number
def withdraw(self, amount):
self.balance -= amount
# Define __eq__ that returns True if the number attributes are equal
def __eq__(self, other):
return self.number == other.number
# Create accounts and compare them
acct1 = BankAccount(123, 1000)
acct2 = BankAccount(123, 1000)
acct3 = BankAccount(456, 1000)
print(acct1 == acct2)
print(acct1 == acct3)
<script.py> output: True False
Notice that your method compares just the account numbers, but not balances. What would happen if two accounts have the same account number but different balances? The code you wrote will treat these accounts as equal, but it might be better to throw an error – an exception – instead, informing the user that something is wrong. At the end of the chapter, you’ll learn how to define your own exception classes to create these kinds of custom errors.
Checking class equality
In the previous exercise, you defined a BankAccount
class with a number
attribute that was used for comparison. But if you were to compare a BankAccount
object to an object of another class that also has a number
attribute, you could end up with unexpected results.
For example, consider two classes
class Phone: def __init__(self, number): self.number = number def __eq__(self, other): return self.number == \ other.number pn = Phone(873555333) | class BankAccount: def __init__(self, number): self.number = number def __eq__(self, other): return self.number == \ other.number acct = BankAccount(873555333) |
Running acct == pn
will return True
, even though we’re comparing a phone number with a bank account number.
It is good practice to check the class of objects passed to the __eq__()
method to make sure the comparison makes sense.
Both the Phone
and the BankAccount
classes have been defined. Try running the code as-is using the “Run code” button and examine the output.
- Modify the definition of
BankAccount
to only returnTrue
if thenumber
attribute is the same and thetype()
of both objects passed to it is the same.
Run the code and examine the output again
class BankAccount:
def __init__(self, number, balance=0):
self.number, self.balance = number, balance
def withdraw(self, amount):
self.balance -= amount
# MODIFY to add a check for the type()
def __eq__(self, other):
return (self.number == other.number)
acct = BankAccount(873555333)
pn = Phone(873555333)
print(acct == pn)
Operator | Method |
== | __eq__() |
!= | __ne__() |
>= | __ge__() |
<= | __le__() |
> | __gt__() |
< | __lt__() |
- __hash__() to use objects as dictionary keys and in sets
class BankAccount:
def __init__(self, number, balance=0):
self.number, self.balance = number, balance
def withdraw(self, amount):
self.balance -= amount
# MODIFY to add a check for the type()
def __eq__(self, other):
return ((self.number == other.number) & (type(self) == type(other)))
acct = BankAccount(873555333)
pn = Phone(873555333)
print(acct == pn)
<script.py> output: False
Now only comparing objects of the same class BankAccount
could return True
. Another way to ensure that an object has the same type as you expect is to use the isinstance(obj, Class)
function. This can helpful when handling inheritance, as Python considers an object to be an instance of both the parent and the child class.
Try running pn == acct
in the console (with reversed order of equality).
What does this tell you about the __eq__()
method?
Comparison and inheritance
What happens when an object is compared to an object of a child class? Consider the following two classes:
class Parent:
def __eq__(self, other):
print("Parent's __eq__() called")
return True
class Child(Parent):
def __eq__(self, other):
print("Child's __eq__() called")
return True
The Child
class inherits from the Parent
class, and both implement the __eq__()
method that includes a diagnostic printout.
Which __eq__()
method will be called when the following code is run?
p = Parent()
c = Child()
p == c
When ran in the console the result is:
Child's __eq__() called
True
This shows that when two classes, one as the parent class containing an __eq__() method, and the same for the child case. And when both are turned into an object and the objects are compared, then Python always calls the child’s __eq__()
method when comparing a child object to a parent object.