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)