10 Object Oriented Programming
10.1 Introduction
A Programming Paradigm is a way or style of programming. There are many different paradigms and sometimes they overlap. Some of the main ones are:
The Imperative Paradigm can best be understood as a sequence of commands that are executed one by one as in: first do this, then do that, etc. It can contain loop and branch statements. The programs we have written so far follow this paradigm.
The Functional Paradigm uses mathematical functions as a core principle of building a program. Functions can then be called to evaluate an expression and use the resulting value for further calculations using functions. Functions can also be nested, i.e., a function that calls another function etc. We have used functions earlier, so imagine an almost exclusive use of functions throughout your program.
The Object Oriented Programming (OOP) Paradigm defines objects (i.e., typically a data construct that contains functions that can be called to execute calculations on its data) that send messages to each other.
Objects are like lists or arrays combined with functions that can be called on these lists or arrays. In essence an object is a container holding the data together with methods (or functions) that can be applied to these data. In OOP functions are typically referred to as methods.
import math as m
import time # Imports system time module to time your script
Before using an object, we need to define the blueprint for it. We use the class
method to do this and define the blueprint for an animal-object.
Later we will define more blueprints for sub-classes within animals that inherit some common features of animals and then add more specific features that are particular to that animal.
When defining object we make the rough distinction between data that the object carries - these are usually variables with the prefix .self
and functions that can be called on these data and that are defined within the object. These functions are called methods in object-oriented parlance. In python they are defined very similarly to functions from the previous chapter.
import math as m
class Animal(object):
"""This is the animal class. It's the top class."""
def __init__(self, name = 'Animal Doe', weight = 0.0):
"""Initialize the animal-object with a
default name variable"""
self.name = name
self.weight = weight
def showMyself(self):
"""Method: showMyself()
prints out all the variables."""
print("\n")
print("Hello, let me introduce myself.")
print("-------------------------------")
print("My name is {}".format(self.name))
print("My weight is {} pounds".format(self.weight))
print("-------------------------------\n")
def eatFood(self, foodAmount = 0.0):
"""Method: eatFood(foodAmount=0)
translates food input 'foodAmount'
into additional weight according to the
following formula:
new_weight = old_weight + sqrt(foodAmount)"""
self.weight += m.sqrt(foodAmount)
# Create a class called "Animal"
setClass("Animal",
slots = list(name = "character", weight = "numeric"),
prototype = list(name = "Animal Doe", weight = 0.0))
# Define a method for the class
setMethod("show", "Animal",
function(object) {
cat("\n")
cat("Hello, let me introduce myself.\n")
cat("-------------------------------\n")
cat("My name is", object@name, "\n")
cat("My weight is", object@weight, "pounds\n")
cat("-------------------------------\n\n")
})
# Create an instance of the Animal class
myAnimal <- new("Animal", name = "Joe")
# Use methods of the Animal class
myAnimal
Hello, let me introduce myself.
-------------------------------
My name is Joe
My weight is 0 pounds
-------------------------------
You can look at the class blueprint of the animal object using the help() function
help(Animal)
Help on class Animal in module __main__:
class Animal(builtins.object)
| Animal(name='Animal Doe', weight=0.0)
|
| This is the animal class. It's the top class.
|
| Methods defined here:
|
| __init__(self, name='Animal Doe', weight=0.0)
| Initialize the animal-object with a
| default name variable
|
| eatFood(self, foodAmount=0.0)
| Method: eatFood(foodAmount=0)
| translates food input 'foodAmount'
| into additional weight according to the
| following formula:
| new_weight = old_weight + sqrt(foodAmount)
|
| showMyself(self)
| Method: showMyself()
| prints out all the variables.
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
We next generate an animal object. Here we create the first animal. Its name is Birdy and Birdy weighs 5 gramms.
= Animal(name = 'Birdy', weight = 5.0) animal1
# Create an instance of the Animal class
animal1 <- new("Animal", name = "Birdy", weight = 5.0)
You can access the variables (or embedded data) of the animal using the animal object and the 'dot' notation.
print("The name of the first animal is {}".format(animal1.name))
print("The weight of the first animal is {}".format(animal1.weight))
The name of the first animal is Birdy
The weight of the first animal is 5.0
Or you can simply call the showMyself() method that was defined within the class or blueprint of the animal object.
animal1.showMyself()
Hello, let me introduce myself.
-------------------------------
My name is Birdy
My weight is 5.0 pounds
-------------------------------
# If you call the name of the object it automatically callse
# the show method
animal1
Hello, let me introduce myself.
-------------------------------
My name is Birdy
My weight is 5 pounds
-------------------------------
OOP programming in R is very tedious, I therefore will only provide Python examples from here onwards.
Now we let the animal eat, by calling the method eatFood()
on the bird object.
9)
animal1.eatFood(print("The animal's new weight is = {}".format(animal1.weight))
The animal's new weight is = 8.0
Another way to see the change is to call the same showMyself()
method that we called earlier so you see that the animal1 object is actually storing the change in weight permanently.
animal1.showMyself()
Hello, let me introduce myself.
-------------------------------
My name is Birdy
My weight is 8.0 pounds
-------------------------------
10.2 Inheritance and Subclasses
We next generate a new class that inherits features of the animal class but is more specific. We call it the bird class. A bird is an animal but it has certain bird specific features that other animals do not share. Here is the blueprint.
Note that it is derived from the Animal class so that the Bird class inherits all the methods and variable definitions of its superclass of Animals. However, it adds a couple more variables and methods. Plus it overrides the old method showMyself()
with a more comprehensive output.
Inheritance saves us a lot of typing. As you generate other particual animal objects, you will not have to retype the methods that all animals share. This makes codes more condensed, re-usable and readable.
class Bird(Animal):
"""This is the bird class, derived from the
animal class."""
def __init__(self, name='Bird Doe', weight=0, color='na',
=0):
speed"""Initialize the bird-object calling the animal init method
but also adding additional variables"""
__init__(self, name, weight)
Animal.self.color = color
self.speed = speed
def showMyself(self):
"""Method: showMyself()
prints out all the variables."""
print("\n")
print("Hello, let me introduce myself.")
print("-------------------------------")
print("My name is {}".format(self.name))
print("My weight is {} grams".format(self.weight))
print("My color is {}".format(self.color))
print("My speed is {}".format(self.speed))
print("-------------------------------\n")
def flyTraining(self, workoutLength=0):
"""Method: flyTraining(workoutLength)
Augments the flight speed of the bird as a function
of the bird objects workoutLength:
new_speed = old_speed + log(workoutLength)"""
self.speed += m.log(workoutLength)
# Define a subclass called "Bird" that inherits from the "Animal" class
Bird <- setClass("Bird", contains = "Animal",
slots = list(color = "character", speed = "numeric"),
prototype = list(color = "na", speed = 0))
# Define a method for the class
setMethod("show", "Bird",
function(object) {
cat("\n")
cat("Hello, let me introduce myself.\n")
cat("-------------------------------\n")
cat("My name is", object@name, "\n")
cat("My weight is", object@weight, "pounds\n")
cat("My color is", object@color, "\n")
cat("My speed is", object@speed, "\n")
cat("-------------------------------\n\n")
})
Here we redefined the showMyself()
method from the animal class. When we call it on the bird object, it will use this new definition of the method.
Now let's look at the bird's class blueprint
help(Bird)
Help on class Bird in module __main__:
class Bird(Animal)
| Bird(name='Bird Doe', weight=0, color='na', speed=0)
|
| This is the bird class, derived from the
| animal class.
|
| Method resolution order:
| Bird
| Animal
| builtins.object
|
| Methods defined here:
|
| __init__(self, name='Bird Doe', weight=0, color='na', speed=0)
| Initialize the bird-object calling the animal init method
| but also adding additional variables
|
| flyTraining(self, workoutLength=0)
| Method: flyTraining(workoutLength)
| Augments the flight speed of the bird as a function
| of the bird objects workoutLength:
| new_speed = old_speed + log(workoutLength)
|
| showMyself(self)
| Method: showMyself()
| prints out all the variables.
|
| ----------------------------------------------------------------------
| Methods inherited from Animal:
|
| eatFood(self, foodAmount=0.0)
| Method: eatFood(foodAmount=0)
| translates food input 'foodAmount'
| into additional weight according to the
| following formula:
| new_weight = old_weight + sqrt(foodAmount)
|
| ----------------------------------------------------------------------
| Data descriptors inherited from Animal:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
We next generate our first bird object.
= Bird('Herman', 12, 'blue', 40)
bird1 bird1.showMyself()
Hello, let me introduce myself.
-------------------------------
My name is Herman
My weight is 12 grams
My color is blue
My speed is 40
-------------------------------
# Create an instance of the Bird class
bird1 <- new("Bird", name = "Herman", weight = 12, color = "blue", speed = 40)
# Access slots and use methods of the Bird instance
bird1
Hello, let me introduce myself.
-------------------------------
My name is Herman
My weight is 12 pounds
My color is blue
My speed is 40
-------------------------------
We let the bird eat and then we train him a bit.
=9)
bird1.eatFood(foodAmount=10)
bird1.flyTraining(workoutLength bird1.showMyself()
Hello, let me introduce myself.
-------------------------------
My name is Herman
My weight is 15.0 grams
My color is blue
My speed is 42.30258509299404
-------------------------------
We next generate another bird, but this time we leave out some of the features when we generate it. Python will then simply assign the default values that we defined in the class blueprint above.
= Bird('Tweets', speed=12)
bird2 bird2.showMyself()
Hello, let me introduce myself.
-------------------------------
My name is Tweets
My weight is 0 grams
My color is na
My speed is 12
-------------------------------
10.3 Generating Multiple Objects
Generating a list full of bird objects from two lists with names and weights of animals, but no colors or speeds
= []
birdObjectList = ['Birdy', 'Chip', 'Tweets', 'Feather', 'Gull']
name_list = [4.3, 2.3, 5.6, 5.0, 15.3]
weight_list
for i, (name, weight) in enumerate(zip(name_list, weight_list)):
print("Nr. {}: name = {}, weight = {}".format(i, name, weight))
# Here we create the bird objects and store them in
# the birdObjectList
=name, weight=weight))
birdObjectList.append(Bird(name
# Here we print what we have so far
Nr. 0: name = Birdy, weight = 4.3
Nr. 1: name = Chip, weight = 2.3
Nr. 2: name = Tweets, weight = 5.6
Nr. 3: name = Feather, weight = 5.0
Nr. 4: name = Gull, weight = 15.3
print(birdObjectList)
[<__main__.Bird object at 0x7f550409dad0>, <__main__.Bird object at 0x7f550406f2d0>, <__main__.Bird object at 0x7f55040973d0>, <__main__.Bird object at 0x7f550409db10>, <__main__.Bird object at 0x7f550409db50>]
# Create a list to store Bird objects
birdObjectList <- list()
# Define the lists of attributes
name_list <- c('Birdy', 'Chip', 'Tweets', 'Feather', 'Gull')
weight_list <- c(4.3, 2.3, 5.6, 5.0, 15.3)
# Loop to create Bird objects and store them in the list
for (i in 1:length(name_list)) {
birdObjectList[[i]] <- new("Bird", name = name_list[i], weight = weight_list[i])
cat("Nr.", i, ": name =", birdObjectList[[i]]@name, "weight =", birdObjectList[[i]]@weight, "\n")
}
Nr. 1 : name = Birdy weight = 4.3
Nr. 2 : name = Chip weight = 2.3
Nr. 3 : name = Tweets weight = 5.6
Nr. 4 : name = Feather weight = 5
Nr. 5 : name = Gull weight = 15.3
# Print the list of Bird objects
print(birdObjectList)
[[1]]
Hello, let me introduce myself.
-------------------------------
My name is Birdy
My weight is 4.3 pounds
My color is na
My speed is 0
-------------------------------
[[2]]
Hello, let me introduce myself.
-------------------------------
My name is Chip
My weight is 2.3 pounds
My color is na
My speed is 0
-------------------------------
[[3]]
Hello, let me introduce myself.
-------------------------------
My name is Tweets
My weight is 5.6 pounds
My color is na
My speed is 0
-------------------------------
[[4]]
Hello, let me introduce myself.
-------------------------------
My name is Feather
My weight is 5 pounds
My color is na
My speed is 0
-------------------------------
[[5]]
Hello, let me introduce myself.
-------------------------------
My name is Gull
My weight is 15.3 pounds
My color is na
My speed is 0
-------------------------------
We can access the bird objects created in this list by list indexation and calling on methods defined on the bird class.
Let's graph the 3rd bird in the list and remember that indexing starts at 0.
print('birdObjectList[2] =', birdObjectList[2])
print('birdObjectList[2].name =', birdObjectList[2].name)
print('birdObjectList[2].weight =', birdObjectList[2].weight)
print('birdObjectList[2].color =', birdObjectList[2].color)
print('birdObjectList[2].speed =', birdObjectList[2].speed)
birdObjectList[2] = <__main__.Bird object at 0x7f55040973d0>
birdObjectList[2].name = Tweets
birdObjectList[2].weight = 5.6
birdObjectList[2].color = na
birdObjectList[2].speed = 0
cat('birdObjectList[[2]]@name =', birdObjectList[[2]]@name, '\n')
cat('birdObjectList[[2]]@weight=', birdObjectList[[2]]@weight, '\n')
cat('birdObjectList[[2]]@color =', birdObjectList[[2]]@color, '\n')
cat('birdObjectList[[2]]@speed =', birdObjectList[[2]]@speed, '\n')
birdObjectList[[2]]@name = Chip
birdObjectList[[2]]@weight= 2.3
birdObjectList[[2]]@color = na
birdObjectList[[2]]@speed = 0
Or we could have also done:
2].showMyself() birdObjectList[
Hello, let me introduce myself.
-------------------------------
My name is Tweets
My weight is 5.6 grams
My color is na
My speed is 0
-------------------------------
birdObjectList[[2]]
Hello, let me introduce myself.
-------------------------------
My name is Chip
My weight is 2.3 pounds
My color is na
My speed is 0
-------------------------------
- Object oriented programming
- Methods and functions
- Attributes and variables
- Generate a student class with attributes age, GPA, and gender
- Instantiate 5 students