0% found this document useful (0 votes)
146 views

Object Oriented Programming in Python

The document discusses object oriented programming in Python. It describes how OOP allows modeling computational problems as objects that encapsulate data and functions. A class acts as a template for creating objects with associated attributes and functions. Python fully supports OOP with classes, inheritance, and objects. It provides an example of creating shape classes like rectangles and circles that inherit from both a base Shape class and matplotlib primitives to leverage their rendering capabilities. The class implementation encapsulates object properties and behaviors better than a simple function approach.

Uploaded by

Engr Nasir Shah
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
146 views

Object Oriented Programming in Python

The document discusses object oriented programming in Python. It describes how OOP allows modeling computational problems as objects that encapsulate data and functions. A class acts as a template for creating objects with associated attributes and functions. Python fully supports OOP with classes, inheritance, and objects. It provides an example of creating shape classes like rectangles and circles that inherit from both a base Shape class and matplotlib primitives to leverage their rendering capabilities. The class implementation encapsulates object properties and behaviors better than a simple function approach.

Uploaded by

Engr Nasir Shah
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 10

Object Oriented Programming in Python

Object Oriented Programming (OOP) is a programming paradigm that allows modeling of


computational solutions to programming problems in terms of objects and their interactions. It
encapsulates data (i.e., attributes) and functions (i.e., behaviors) of objects into components called
classes. A class acts as a template or a blueprint for creating objects that have attributes and
functions associated with that class. Python is a fully object oriented programming lagnguage that
support multiple inheritance. You should first read more about classes in the python documentation.
This is a required reading as it will tell you, among other things, about:

 How objects are created


 How Python classes are created on the run time
 How can they be modified after creation
 How Python supports multiple inheritance
 How everything in a class in Python is public

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.

Also note that render uses the add_artist function of the axes class. add_artist expects an


object of class Artist. However, myShape is not an sub-class of Artist. As a consequence, you
cannot render an object of the class myShape. You can try this! This is done by design -- we will be
deriving (i.e., inheriting) classes from myShape which will also inherit from Artist or any of its sub-
classes. For example, our axis aligned rectangle class myAARectangle is inherited simultaneously
from myShape and matplotlib.patches.Rectangle.
The inheritance of myAARectangle from myShape allows it to have the render function and
the name property which are absent from matplotlib.patches.Patches. Note that we do not
explicitly set the name property within this class. This is because we make a call to the base class
initializer which does this for us.

The inheritance of myAARectangle from matplotlib.patches.Rectangle allows us to utilize


matplotlib to render the objects. This minimizes code re-writing. Also notice
that myAARectangle has no properties of its own -- all its properties and functions
(except __init__) are inherited. This is because we re-use the properties
of matplotlib.patches.Rectangle such as height, width, xy (location of the rectangle's
bottom left corner in the axis). One can possibly specify other properties (such as color or fill)
of matplotlib.patches.Rectangle as keyword arguments. However, the use
of height, width and xy in __init__ forces these to be specified even if as default argument
values. This is done by making a call to the base class initializer
as Rectangle.__init__(self,xy=xy,width=width,height=height,**kwargs).

When an object of myAARectangle is created using rg = myAARectangle(name='rect-


1',xy=[0, 0], width=0.5, height=0.5,color='r'), the object is named rect-1 and it is
located at [0 0] with a width equal to height of 0.5 and red color. This statement calls the initializer
of myAARectangle which does the rest of the processing by making calls to base class initializers.

When we call rg.render(ax) with ax as an argument, the rectangle gets added to the


axes ax through a call to the baseclass render which now works because the object being passed
fulfills the requirements of the add_artist function of Axes.

The myCircle class makes use of exactly similar concepts.


In [27]:
from matplotlib.patches import Rectangle,Circle, Polygon
import matplotlib.pyplot as plt
import math

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

#Let's make a figure, specify its axis ranges


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()
rg.render(ax)
yc.render(ax)

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).

Hint: A star is a polygon.

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)

#properties of the star I want


smax=10 # number of stars
rmax=0.2 # maximum radius
# Let's draw
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()
Names = []
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
makeStar(ax,xy=xy,n=n,theta=theta,ri=ri,ro=ro,color=c)
Names.append(name)

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])

#properties of the star I want


smax=10 # number of stars
rmax=0.2 # maximum radius
# Let's draw
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()
Stars = []
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
star = myStar(name,xy=xy,n=n,theta=theta,ri=ri,ro=ro,color=c)
Stars.append(star)
star.render(ax)

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()

star1 = myStar('close star',xy=(0.2,0.2))


star2 = myStar('close star',xy=(0.4,0.2),color='k')
star1.render(ax)
star2.render(ax)
plt.show()

In [31]:
ax = plt.gca()

star1.move(xy=(0.8,0.8))
star1.render(ax)
plt.show()

Exercise: Make a scene


Implemente a class myScene whose objects can display collections of graphical primitves as a
scene. For example: the code below should generate the same figure as above:

scene = myScene(name = 'simple scene',contents=[rg,yc],vport=[0,1,0,1])


scene.show(figid = 0)

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)

Exercise: Make the Pakistani Flag


1. Use the class myStar.
2. Implement a class myCrescent. A crescent can be drawn by an overlap of two circles
with one cricle in drawn in the foreground color and the other one on top of this one in the
background color.
3. Make the Flag (or any other scene of your liking!). The details for making the flag are
given below.

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()

You might also like