object oriented programming

objects

each object has

  • a type

  • an internal data representation

  • a set of procedures for interactions

Everything in python is an object

Objects are data abstractions

Advantages of OOP

  • bundle data into packages together with procedures

  • divide-and-conquer development: increase modularity

  • classes make it easy to reuse code

  • access info in a consistent manner

  • add layers of complexity

  • like functions, classes are a mechanism for decomposition and abstractions

create a classes

  • define class name

  • define attributes: data + methods

create an instance of a class

special method init to initialize with data attributes

class Coordinate(obejct):
    def __init__(self,x,y): # to create instance, self: placeholder to refer to an instance of class, x,y: data initialize a coord object
        self.x = x
        self.y = y 
    def distance(self,other): # self: any instance, other: another parameter
        x_diff_sq = (self.x - other.x)**2
        y_diff_sq = (self.x - other.x)**2
        return (x_diff_sq + y_diff_sq)**0.5
        
c = Coordinate(3,4)

how to use a method

2 ways

conventional:

c = Coordinate(3,4)
zero = Coordinate(0,0)
print(c.distance(zero)) # c: object to call method on, distance: method, zero: parameter not including self (self implied c)

equiv

c = Coordinate(3,4)
zero = Coordinate(0,0)
print(Coordinate.distance(c,zero)) # c and zero are parameters

define your own print

def __str__(self):
    return "<"+str(self.x)+","+str(self.y)+">"
#can use like this
c = Coordinate(3,4)
print(c)

special operators: double underscores before and after

__add__
__sub__
__eq__
__lt__
__len__
__str__

getter and setters: access/set attributes

important for information hiding:

dot notation can access attributes but it’s better to use getters and setters

outside of class, use getters and setters

  • good style

  • easy to maintain code

  • prevents bugs

class Animal(object):
    def __init__(self,age):
        self.years = age
    def get_age(self):
        return self.years
a = Animal(3)
a.get_age()

default arguments: used if no actual argu giving

def set_name(self,newname = ""):
    self.name = newname

hierarchies

parent class (superclass)

child class (subclass)

inherits all data and behaviors of parent class

add more information

add more behavior

override behavior


class Animal(object):
    def __init__(self, age):
        self.age = age
        self.name = None
    def get_age(self):
        return self.age
    def get_name(self):
        return self.name
    def set_age(self, newage):
        self.age = newage
    def set_name(self, newname=""):
        self.name = newname
    def __str__(self):
        return "animal:"+str(self.name)+":"+str(self.age)
        
print("\n---- animal tests ----")
a = Animal(4)
print(a)
print(a.get_age())
a.set_name("fluffy")
print(a)
a.set_name()
print(a)



#################################
## Inheritance example 
#################################
class Cat(Animal):
    def speak(self):
        print("meow")
    def __str__(self):
        return "cat:"+str(self.name)+":"+str(self.age)
    
print("\n---- cat tests ----")
c = Cat(5)
c.set_name("fluffy")
print(c)
c.speak()
print(c.get_age())
#a.speak() # error because there is no speak method for Animal class

for an instance of class, look for a method name in current class definition

if not found, look for method name up the hierarchy (parent -> grandparent, …)

use first method up the hierarchy that you found the method name

class variables: value shared by all instances

example to give unique id to each new rabbit instance

class Rabbit(Animal):
    # a class variable, tag, shared across all instances
    tag = 1
    def __init__(self, age, parent1=None, parent2=None):
        Animal.__init__(self, age)
        self.parent1 = parent1
        self.parent2 = parent2
        self.rid = Rabbit.tag
        Rabbit.tag += 1
    def get_rid(self):
        # zfill used to add leading zeroes 001 instead of 1
        return str(self.rid).zfill(3)
    def get_parent1(self):
        return self.parent1
    def get_parent2(self):
        return self.parent2
    def __add__(self, other):
        # returning object of same type as this class
        return Rabbit(0, self, other)
    def __eq__(self, other):
        # compare the ids of self and other's parents
        # don't care about the order of the parents
        # the backslash tells python I want to break up my line
        parents_same = self.parent1.rid == other.parent1.rid \
                       and self.parent2.rid == other.parent2.rid
        parents_opposite = self.parent2.rid == other.parent1.rid \
                           and self.parent1.rid == other.parent2.rid
        return parents_same or parents_opposite
    def __str__(self):
        return "rabbit:"+ self.get_rid()

# included the tests since this is also new to me
print("\n---- rabbit tests ----")
print("---- testing creating rabbits ----")
r1 = Rabbit(3)
r2 = Rabbit(4)
r3 = Rabbit(5)
print("r1:", r1)
print("r2:", r2)
print("r3:", r3)
print("r1 parent1:", r1.get_parent1())
print("r1 parent2:", r1.get_parent2())

print("---- testing rabbit addition ----")
r4 = r1+r2   # r1.__add__(r2)
print("r1:", r1)
print("r2:", r2)
print("r4:", r4)
print("r4 parent1:", r4.get_parent1())
print("r4 parent2:", r4.get_parent2())

print("---- testing rabbit equality ----")
r5 = r3+r4
r6 = r4+r3
print("r3:", r3)
print("r4:", r4)
print("r5:", r5)
print("r6:", r6)
print("r5 parent1:", r5.get_parent1())
print("r5 parent2:", r5.get_parent2())
print("r6 parent1:", r6.get_parent1())
print("r6 parent2:", r6.get_parent2())
print("r5 and r6 have same parents?", r5 == r6)
print("r4 and r6 have same parents?", r4 == r6)