I love a good analogy! Python delivers a good one with its class / subclass relationship. Just like father and son, in classes and subclasses the child can:
- Inherit characteristics, in this case attributes and methods
- Change some of the characteristics (and even create new ones!)
From a programmers point of view, implementing classes in Python is a dead easy task. I'll come upfront and confess my love for Python: it is versatile, powerful, elegant, and so, so easy to use. Hence, let's start with a simple example:
You work for a small animal clinic. You are tasked with creating a combination of class / subclass that will be able to hold the information of each patient. The parent class defines an animal patient, while the subclasses define a specific type of animal (e.g. dog) patient.
**DISCLAIMER: THIS CODE WAS CREATED SPECIFICALLY FOR THIS SLOG. IT NEEDS MORE TESTING! IT WAS CREATED TO SHOW THE POWER OF CLASSES AND SUBCLASSES. YOU MAY USE SNIPPETS OF THIS CODE FREELY, BUT IF YOU WISH TO USE IT ALL, PLEASE LET ME KNOW! IF YOU HAVE ANY QUESTIONS ABOUT ANY STEP, FEEL FREE TO CONTACT ME. FINALLY, IF YOU USE THIS FOR AN ASSIGNMENT, THINK TWICE! IT CONSTITUTES ACADEMIC FRAUD.**
-----------------------------------------
#so that we can use the datetime type for recording dates
import datetime
#dict for switch-like method, inspired by
#Vincent's Slog!
#cool SLOG! go check it out!
dogshotlist = {1: "Booster Shot 1",
2: "Booster Shot 2",
3: "Booster Shot 3",
4: "Rabies",
5: "Bordatella"}
class AnimalPatient():
def __init__(self, owner, name, breed, bdyear, bdmonth, bdday):
"""
(AnimalPatient, str, str, str, int, int, int) -> NoneType
"""
self.owner = owner
self.name = name
self.bday = datetime.date(bdyear, bdmonth, bdday)
self.breed = breed
self.shothistory = {}
self.appointment = False
self.appdate = datetime.date(2015, 1, 1)
def createapp(self, appyear, appmonth, appday):
"""
(AnimalPatient, int, int, int) -> NoneType
"""
self.appdate = datetime.date(appyear, appmonth, appday)
self.appointment = True
def addshot(self, shotnum, shyear, shmonth, shday):
"""
(AnimalPatient, int, int, int, int) -> NoneType
Create the next sequential key in dict shothistory that holds
the shot's name, determined by shotnum, and the date with shyear
as year, shmonth as month, and shday as day
Needs to be implemented in a subclass.
"""
#different animals have different shots, so we need a subclass
#to specify what needs to be done, so we raise an error
#if this is not used within a subclass with a working method
raise NotImplementedError('Subclass needed')
def missingshots(self):
"""
(AnimalPatient) -> str
Return string that displays the missing shots of the patient.
Needs to be implemented in a subclass.
"""
#just like in add shot, we need something specific to an animal
#subclass to execute this method
raise NotImplementedError('Subclass needed')
def __repr__(self):
"""
(AnimalPatient) -> str
Return string that represents how this object was initialized
"""
#string representation of how the object was initialized
return "AnimalPatient(owner = {}, name = {}, breed = {}, bdyear = {}, bdmonth = {}, bdday = {})".format(self.owner, self.name, self.breed, str(self.bday)[:4], str(self.bday)[5:7], str(self.bday)[8:10])
def __str__(self):
"""
(AnimalPatient) -> str
Return string that displays the information held by this object
"""
#not all animals have breeds! so you can make a different
#__str__ method for those animals
info = "Owner name: {}\nPet name: {}\nBirthday: {}\nBreed: {}".format(self.owner, self.name, self.bday, self.breed)
shots = ""
i = 1
for shot in self.shothistory:
shots += shot + ". " + self.shothistory[shot] + "\n"
if self.appointment:
appointment = "\nAppointment Date: {}".format(self.appdate)
else:
appointment = "\n**No Appointment Recorded**\n"
return info + shots + appointment
def __eq__(self, other):
self.owner = other.owner
self.name = other.name
self.bday = other.bday
self.shothistory = other.shothistory
self.appointment = other.appointment
self.appdate = other.appdate
#we mention AnimalPatient here so that we can inherit its characteristics
class DogPatient(AnimalPatient):
def __init__(self, owner, name, breed, bdyear, bdmonth, bdday):
"""
(DogPatient, str, str, str, int, int, int) -> NoneType
"""
#initializes the parent AnimalPatient type to inherit
#its methods and attributes
AnimalPatient.__init__(self, owner, name, breed, bdyear, bdmonth, bdday)
#dogs need anual deworming, hence we make an extra attribute here
self.lastdeworm = datetime.date(2015, 1, 1)
#this is a method exclusive of DogPatient
def addlastdeworm(self, dwyear, dwmonth, dwday):
"""
(DogPatient, int, int, int) -> NoneType
Change lastdeworm date to dwyear for year, dwmonth for month, and
dwday for day
"""
self.lastdeworm = datetime.date(dwyear, dwmonth, dwday)
def addshot(self, shotnum, shyear, shmonth, shday):
"""
(DogPatient, int, int, int, int) -> NoneType
Create the next sequential key in dict shothistory that holds
the shot's name, determined by shotnum, and the date with shyear
as year, shmonth as month, and shday as day
"""
if len(self.shothistory) == 0:
self.shothistory[1] = (dogshotlist[shotnum], datetime.date(shyear, shmonth, shday))
else:
self.shothistory[len(self.shothistory) + 1] = (dogshotlist[shotnum], datetime.date(shyear, shmonth, shday))
def missingshots(self):
"""
(AnimalPatient) -> str
Return string that displays the missing shots of the patient.
Needs to be implemented in a subclass.
"""
#creates shallow copy of the dogshotlist to avoid mutation
missingshots = dict(dogshotlist)
missingstring = "List of missing shots:\n"
for i in self.shothistory:
for j in missingshots:
if self.shothistory[i][0] == missingshots[j]:
del missingshots[j]
else:
pass
if len(missingshots) == 0:
missingstring = "\n**No missing shots**\n"
else:
k = 1
for l in missingshots:
missingstring += str(k) + ". " + missingshots[l] + "\n"
k += 1
return missingstring
#DogPatient has a new attribute, so we need a new eq method
def __eq__(self, other):
self.owner = other.owner
self.name = other.name
self.bday = other.bday
self.shothistory = other.shothistory
self.appointment = other.appointment
self.appdate = other.appdate
#this new attribute forces us to create a new __eq__
self.lastdeworm = other.lastdeworm
#alternatively, we could call
#the method AnimalPatient.__eq__(self, other) and add the last line
#for the extra attribute
#we do need to change repr since the class has a different name
#we do not make a str method since AnimalPatient has a pretty strong
#and useful one
def __repr__(self):
"""
(DogPatient) -> str
Return string that represents how this object was initialized
"""
#same as for AnimalPatient, except that we need to change
#AnimalPatient to DogPatient
return "DogPatient(owner = {}, name = {}, breed = {}, bdyear = {}, bdmonth = {}, bdday = {})".format(self.owner, self.name, self.breed, str(self.bday)[:4], str(self.bday)[5:7], str(self.bday)[8:10])
-----------------------------------------
Let's test the code!
>>> d = DogPatient('Jose', 'Miguelito', 'Poodle', 2015, 9, 15)
>>> print(d)
Owner name: Jose
Pet name: Miguelito
Birthday: 2015-09-15
Breed: Poodle
**No Appointment Recorded**
>>> print(d.missingshots())
List of missing shots:
1. Booster Shot 1
2. Booster Shot 2
3. Booster Shot 3
4. Rabies
5. Bordatella
>>> d.createapp(2015, 4, 5)
>>> print(d)
Owner name: Jose
Pet name: Miguelito
Birthday: 2015-09-15
Breed: Poodle
Appointment Date: 2015-04-05
As you can see, the function createapp() was inherited from AnimalPatient, and missingshots() is specific to DogPatient.
Try taking this class / subclass combo for a spin ;)
Great analogy! It really helped me to understand classes and subclass by thinking of them in terms of parent class and child class, and inheriting methods etc as a child would inherit genes or characteristics.
ReplyDeleteThanks! I'm not sure if we could say that nature is so wise, that it can (and should!) be mimicked in code :)
ReplyDeleteThis comment has been removed by the author.
ReplyDelete