Lesson 4 - Object-Oriented Programming

What is Object-Oriented Programming?

Object oriented programming is a way for us to structure our code such that we have objects (they come from classes), and each object has certain attributes and functions that are unique to that object. A good example is the dog object. What are some features of the dog object?

Well, we know that all dogs:

  • Have 4 legs
  • Have 1 tail
  • Can bark

Instantiating an Object

If we were to translate this to actual python code, we would get something that looks like this:

class Dog: #The dog class. This is also known as the dog object.
  # Class attributes: These are features that are common to all dogs
  numLegs = 4
  numTails = 1
  canBark = True

  def __init__(self , breedName , furColor , eyeColor): #This is the constructor.
  # Instance attributes: are unique to each instance and can
  # change depending on the initial constructor parameters.  
    self.breedName = breedName
    self.furColor = furColor
    self.eyeColor = eyeColor

A constructor literally “makes” a new instance of the object every time it is called. What is an instance?. You can think of instances as being copies of the object, but with certain aspects that make that instance unique as compared to other instances of the same object. Each of these instances are unique and take a different location in the computer’s memory.

To instantiate an object we can simply call the constructor.


myDog = Dog("Labrador, blonde , blue")
print("My dog is a " , myDog.breedName)
print("My dog has " , myDog.furColor , " fur")
print("My dog has " , myDog.eyeColor , " eyes")
if(myDog.canBark):
  print("Yes my dog can bark!")
My dog is a Labrador
My dog has blonde fur
My dog has blue eyes
Yes my dog can bark!

Inheritance

When we think about inheritance, it’s a good thing to think about parents and children. Let’s take our dog analogy as an example. I can start with a parent class (a parent object) called “Dog”. Dog is very broad, but underneath this Dog umbrella, there are many different types of dogs. Instead of defining the breed, fur color, and eye color in the original overarching Dog class, we can create a child class called Labrador that inherits features from its parent Dog class. See the code example below.


class Dog: #The parent class
  numLegs = 4
  numTails = 1
  canBark = True
  mood = "neutral"

  def likesToPlayBall(self):
    if (self.mood == "happy"):
      return True
    return False
  def printLikesToPlayBall(self):
    if(self.likesToPlayBall()):
      print("This dog likes to play ball!")
    else:
      print("This dog does not like to play ball!")



class Labrador(Dog): #The child class inherits from Dog
  furColor = "blonde"
  eyeColor = "blue"
  name = "Labrador"


  def __init__(self, mood):
    self.mood = mood



class GoldenRetriever(Dog): #The child class inherits from Dog
  furColor = "gold"
  eyeColor = "brown"
  name = "Golden Retriever"

  def __init__(self, mood):
    self.mood = mood

Now, again, let’s try instantiating some Labradors and Golden Retrievers. Note that the original Dog class itself does not have a constructor. So it’s more of a static class. This means that it can’t be instantiated.


myLab = Labrador("happy")
myRetriever = GoldenRetriever("sad")

print("Labrador":)
myLab.printLikesToPlayBall()
print("Golden Retriever")
myRetriever.printLikesToPlayBall()
Labrador:
This dog likes to play ball!
Golden Retriever:
This dog does not like to play ball!

Building an Object Oriented Contacts Database

Now that we have learned a little about objects, inheritance, and other concepts, a cool project would be to create a Contacts database similar to the one you have on your phone. We can think about each contact as being a unique instance of the Contact class. There are key features that make up a Contact:

  • First Name
  • Last Name
  • Phone Number
  • Email (optional)
  • Company (optional)

We also need to store each contact somehow. We can do this by creating a Contact list, which is essentially a list of Contact objects. So we would have ContactList = [Contact].

First, let’s render the Contact Class, with its constructor:

class Contact:

  def __init__(self, firstName , lastName , phoneNumber , email=None , company=None):
    self.firstName = firstName
    self.lastName = lastName
    self.fullName = firstName , " " , lastName
    self.phoneNumber = phoneNumber
    self.email = email if email is not None else None
    self.company = company if company is not None else None

Here I would like to briefly pause and discuss default arguments. We use this technique when we have one function that can take multiple inputs, and performs different actions depending on its parameters when it is called. Here, the parameters of the Contact constructor can have four different combinations, so we take all this in account when writing our constructor. Note that there is no method overloading in python.

Contact("firstName" , "lastName" , "phoneNumber" , "email" , "company") #First option
Contact("firstName" , "lastName" , "phoneNumber" , company="company") #second option
Contact("firstName" , "lastName" , "phoneNumber" , email="email", ) #Third option
Contact("firstName" , "lastName" , "phoneNumber") #Fourth option

Note how for company and email we have to be explicit, as if the argument is not explicitly specified, then the order in which we input arguments does matter.

Now we can work on developing our Contact List. Note, we are choosing to use a list instead of dictionary data structure because the keys in dictionaries must be unique. We know that we can have multiple contacts with the same first name, same last name, or both! So it makes more sense to use a list where repeats are allowed.

Below is a short script that instantiates a new contact based on the code above, and then adds it to a list.

myContactList = [] #instantiate the new Contact list
newContact = Contact("Zaira" , "Wasim" , "+91 22 1234 5678" , "zaira.wasim@example.com" , "Acting School")
secondContact = Contact("Ahmed" , "Zewail" , "+1 123 456 7890" , email="ahmed.zewail@example.edu")
myContactList += [newContact , secondContact]

Now we want to search our contacts, find the contact we are looking for by their full name, and, see what their email address is!

Let’s break this problem down into some pseudo-code:

1. Do a binary recursive search to find the first instance of the contact whose name matches our query.
2. If the contact has an email, get their email
3. Print their email to the screen!
#First, sort the list based on the first name

myContactList.sort(key=lambda x: x.firstName)

# The binary search algorithm
def binarySearch (input, left, right, x):

    # Check base case
    if right >= left:

        mid = int(left + (right - left)/2)

        # If element is present at the middle itself
        if input[mid].firstName == x:
            return mid

        # If element is smaller than mid, then it can only
        # be present in left sub-input
        elif input[mid].firstName > x:
            return binarySearch(input, left, mid-1, x)

        # Else the element can only be present in right sub-input
        else:
            return binarySearch(input, mid+1, right, x)

    else:
        # Element is not present in the input
        return -1

Now, let’s call this method and retrieve the email. I want to look for Zaira Wassim’s email, so I’ll look for her first name.

indexOfContact = binarySearch(myContactList , 0 , len(myContactList) - 1 , "Zaira")
selectedContact = myContactList[indexOfContact]
if selectedContact.email is not None:
  print("Zaira's email address is:" , selectedContact.email)
else:
  print("Zaira does not have an email stored!")
Zaira's email address is: zaira.wasim@example.com

Building a Song Player

In this assignment, we will take what we have learned about python and object-oriented programming to create a small program to store information about and play some of our favorite songs!

Previous
Next