Object Oriented Programming in Python
Object Oriented Programming in Python
Below, we take a simple example of a shape class hierarchy. For drawing purposes we inherit the
shape objects from corresponding shape primitives in matplotlib.patches. We create a
myShape object which has a single property called name which is the name of the shape object we
want to draw. It has the following behaviors:
Render: It takes an axes as input and draws the shape on that axes
__str__ and __str__ : Both these functions return a string representation of the object
so that we can print it (Try your first implementation without these!)
Each class has a __init__ method. This method is called the initializer. It essentially creates an
object of that class. You can specify the properties of the object within this function. For example, for
the myShape object, we assign the name parameter to the self.name property of the object after
checking that it is not empty or None or composed entirely of whitespaces.
The self variable represents the instance of the object itself. When you make an instance
of myShape class as m = myShape (name = 'my first star'), the selfobject is not passed
as an explicit argument. However, what Python does is that it creates an object and passes it as the
first argument (self). m then becomes an alias to that object. Analogs to the self exist in other
languages but for most of such languages, such C++, these are typically implicit. However, Python
defines it an explicit manner as it greatly simplifies a number of things such as function overriding,
multiple inheritance, naming confusions, etc.
class myShape:
def __init__(self,name):
if name is None or type(name)!=type('') or not name.strip():
raise ValueError('Shape name cannot be empty!')
self.name=name
def render(self,ax):
"""
Add the current object to the figure axes
"""
ax.add_artist(self)
def __str__(self):
"""
Return a string form of the object
"""
return self.name
def __repr__(self):
"""
Return the representation of the object
"""
return self.__str__()
class myAARectangle(myShape,Rectangle):
"""
Axis Aligned Rectangle
"""
def __init__(self,name,xy=(0,0),width=1,height=None,**kwargs):
if height is None:
height=width
myShape.__init__(self,name=name) #calling myShape initializer for
setting name properly
# Note that we pass `self` as an explicit argument in this call.
Rectangle.__init__(self,xy=xy,width=width,height=height,**kwargs)
#Rectangle initializer for drawing
# Note that we pass `self` as an explicit argument in this call.
class myCircle(myShape,Circle):
def __init__(self,name,xy=(0,0),radius=1,**kwargs):
myShape.__init__(self,name=name)
Circle.__init__(self,xy=xy,radius=radius,**kwargs)
if __name__=='__main__':
rgb_green=(0,0.4,0.2)
rg=myAARectangle(name='rect',xy=[0, 0], width=0.5,
height=0.5,color=rgb_green)
# Note that we pass `self` as an implicit argument in this call.
yc=myCircle(name='circle',xy=[0.5, 0.5], radius=0.2, color='y')
print rg
print yc
plt.show()
rect
circle
Assignment
Draw 10 stars of random number of edges at random locations with random sizes and colors. Each
star should have a name which is assigned by the user. After drawing, print all the properties (name,
location, color and edges) of all stars. First develop a function based implementation of this task.
Then impelementd a class myStar which can generate an n-pointed star (the star in Pakistan's flag
has n = 5) at a given location with its first pointed tip oriented at a given angle from the x-axis (for the
star in Paksitan's flag, this angle is 45 degrees).
You should also do a mental comparison of the implementation of this as a class and as a function.
How is the class implementation better than the function?
Solution
Function Implementation
In [28]:
from random import random as rnd
from random import randint as rndi
def makeStar(ax, xy=(0,0),n=5,theta=45,ri=0.25,ro=0.5,color='r',**kwargs):
dt=2*math.pi/n
t=theta*math.pi/180.0
x0,y0=xy
P=[]
for i in range(n):
pout=x0+ro*math.cos(t),y0+ro*math.sin(t)
pin=x0+ri*math.cos(t+dt/2.0),y0+ri*math.sin(t+dt/2.0)
t+=dt
P.extend([pout,pin])
star = Polygon(P, closed=True,color=color,**kwargs)
ax.add_artist(star)
print Names
['rand-0', 'rand-1', 'rand-2', 'rand-3', 'rand-4', 'rand-5', 'rand-6', 'rand-
7', 'rand-8', 'rand-9']
Class Implementation
In [29]:
from random import random as rnd
from random import randint as rndi
class myStar(myShape,Polygon):
def __init__(self,name,xy=(0,0),n=5,theta=45,ri=0.25,ro=0.5,color='r'):
myShape.__init__(self,name=name)
self.edges = n
self.color = color
dt=2*math.pi/n
t=theta*math.pi/180.0
x0,y0=xy
P=[]
for i in range(n):
pout=x0+ro*math.cos(t),y0+ro*math.sin(t)
pin=x0+ri*math.cos(t+dt/2.0),y0+ri*math.sin(t+dt/2.0)
t+=dt
P.extend([pout,pin])
self.P=P # list of points
Polygon.__init__(self,self.P, closed=True,color = color)
def __repr__(self):
return self.name + ' : ' + str(self.edges) + ' ' + str(self.color) + '
' + str(self.xy[0])
print Stars
Stars[0].color = 'r' # print color of the first star
[rand-0 : 5 (0.2718243743002856, 0.7852852091686071, 0.7185928254488492)
[ 0.68948724 0.38879682], rand-1 : 8 (0.006450660671438846,
0.6369253483591193, 0.8573534943027019) [ 0.30947324 0.3306786 ], rand-2 : 3
(0.0026414330479413994, 0.08920406819115756, 0.5387486548037038) [ 0.85263616
0.21194561], rand-3 : 16 (0.30704930024689847, 0.719528489061925,
0.7061641002629729) [ 0.84183384 0.67943855], rand-4 : 4
(0.017375982404427837, 0.6275570559579229, 0.6839311143270719) [ 0.28523798
0.53672 ], rand-5 : 11 (0.936865199493178, 0.16600224493064297,
0.8159528081778056) [ 0.28664692 0.3144011 ], rand-6 : 7
(0.15286283011857815, 0.8791271617748918, 0.8299536399976313) [ 0.23073774
0.66100898], rand-7 : 5 (0.8869487846367423, 0.7835697660804558,
0.43626185462985445) [ 0.27945123 0.48202088], rand-8 : 4
(0.8333133627455516, 0.8506444713325404, 0.36543854900375505) [ 0.25470865
0.78060703], rand-9 : 13 (0.04592633864040019, 0.1682478140300181,
0.921709148319112) [ 0.11187536 0.80197566]]
The above experiment should have revealed to you that objects can be used to store and organize
data which is hard to do when function programming is used and there a number of data to be
saved. However, this is a very limited use of the object. The real power of the objects lies in their
ability to change their behavior. For example, it is really easy to move a star from its current location
using OOP. It also encapsulates (hides away the implementation details). However, function
programming is not without its benefits. For example, we have created a function which allows us to
write lesser code. This flexibility of Python to mix different programming paradigms is really useful!
In [30]:
def getStarPoints(xy,n,theta,ri,ro):
dt=2*math.pi/n
t=theta*math.pi/180.0
x0,y0=xy
P=[]
for i in range(n):
pout=x0+ro*math.cos(t),y0+ro*math.sin(t)
pin=x0+ri*math.cos(t+dt/2.0),y0+ri*math.sin(t+dt/2.0)
t+=dt
P.extend([pout,pin])
return P
class myStar(myShape,Polygon):
def __init__(self,name,xy=(0,0),n=5,theta=45,ri=0.25,ro=0.5,color='r'):
myShape.__init__(self,name=name)
self.edges = n
self.theta = theta
self.ri = ri
self.ro = ro
self.color = color
P = getStarPoints(xy,self.edges,self.theta,self.ri,self.ro)
Polygon.__init__(self,P, closed=True,color = color)
def __repr__(self):
return self.name + ' : ' + str(self.edges) + ' ' + str(self.color) + '
' + str(self.loc)
def move(self,xy):
P = getStarPoints(xy,self.edges,self.theta,self.ri,self.ro)
#calculate the new points based on new xy
P.append(P[0])
self.set_xy(P)
plt.figure(0)
plt.axis('image')
plt.axis([0,1,0,1])
#let's get the axes and render our objects on it
ax = plt.gca()
In [31]:
ax = plt.gca()
star1.move(xy=(0.8,0.8))
star1.render(ax)
plt.show()
Here, name is the name of the scene, contents is a list (or tuple) containing the objects on the
scene in the order in which they will be drawn and vport is the viewport, i.e., the extents of the
scene. figid is the id of the figure on which the scene is to be drawn.
Solution:
In [32]:
class myScene:
def __init__(self,name,contents=[],vport=[0,1,0,1]):
if name is None or type(name)!=type('') or not name.strip():
raise ValueError('Scene name cannot be empty!')
self.name=name
self.contents=contents
self.vport=vport
def show(self,figid=0):
plt.figure(figid)
plt.axis('image')
plt.axis(self.vport)
ax = plt.gca()
for s in self.contents:
s.render(ax)
plt.show()
def __str__(self):
s=str(self.__class__)+' instance: '+self.name
s+=' with '+str(len(self.contents))+' objects'
return s
def __repr__(self):
return self.__str__()
rgb_green=(0,0.4,0.2)
rg=myAARectangle(name='rect',xy=[0, 0], width=0.5, height=0.5,color=rgb_green)
yc=myCircle(name='circle',xy=[0.5, 0.5], radius=0.2, color='y')
scene = myScene('simple scene',contents=[rg,yc,star1,star2],vport=[0,1,0,1])
scene.show(figid=1)
In [33]:
class myCrescent(myShape):
def __init__(self,name,c0,r0,fcolor,c1,r1,bcolor):
myShape.__init__(self,name=name)
self.c1=myCircle(name=name+':c1',xy=c0,radius=r0,color=fcolor)
self.c2=myCircle(name=name+':c2',xy=c1,radius=r1,color=bcolor)
def render(self,ax):
self.c1.render(ax)
self.c2.render(ax)
def raisePakFlag():
height=2/3.
width=1.
gwidth=0.75*width
wwidth=width-gwidth
rgb_green=(0,0.4,0.2)
rg=myAARectangle(name='r-green',xy=[0, 0], width=width,
height=height,color=rgb_green)
rw=myAARectangle(name='r-white',xy=[0, 0], width=0.25*width,
height=height,color='w')
cr=myCrescent(name='crescent',c0=(wwidth+gwidth/2.0,height/2.),r0=(3/10.)*heig
ht,fcolor='w',c1=(0.67*width,0.56*height), \
r1=(11/40.)*height,bcolor=rgb_green)
x=myStar('star',xy=(0.722*width,0.634*height),ro=0.1*height,ri=0.04*height,col
or='w')
flag=myScene(name='flag',contents=[rg,rw,cr,x],vport=[0,width,0,height])
flag.show()
raisePakFlag()
To celbrate the end of this lecture, let's make a scene of random stars using a generator!
In [34]:
def starGenerator(smax=10,rmax=0.2):
from random import random as rnd
from random import randint as rndi
for sno in range(smax):
name = 'rand:'+str(sno)
n = rndi(2,20)
c = (rnd(),rnd(),rnd()) #color
xy = (rnd(),rnd())
theta = rnd()*360
ri = rnd()*rmax
ro = rnd()*rmax
s = myStar(name=name,xy=xy,n=n,theta=theta,ri=ri,ro=ro,color=c)
yield s
rscene=myScene(name='Stars',contents=starGenerator(smax=20),vport=[0,1,0,1])
rscene.show()