In the object oriented paradigm the data and the methods operating on them are put together into an object.
The blueprint for objects are classes.
Let’s see how it would look in both the procedural and the object oriented way
import numpy
points = [ [1, 2], [4,-2] ]
angle = numpy.pi/4
def rotate(points, angle):
cos = numpy.cos(angle)
sin = numpy.sin(angle)
rotated = []
for x,y in points:
newx = cos * x - sin * y
newy = sin * x + cos * y
rotated.append( [newx, newy] )
return rotated
rotate(points, angle)
[[-0.7071067811865474, 2.121320343559643],
[4.242640687119286, 1.4142135623730947]]
points = [ Point(1,2), Point(4,-2) ]
angle = numpy.pi/4
for p in points:
p.rotate\_by(angle)
points
[Point(-0.7071067811865474,2.121320343559643),
Point(4.242640687119286,1.4142135623730947)]
Point
class definitionclass Point():
def \_\_init\_\_(self, x, y):
self.x = x
self.y = y
def rotate\_by(self, angle):
cos = numpy.cos(angle)
sin = numpy.sin(angle)
newx = cos * self.x - sin * self.y
newy = sin * self.x + cos * self.y
self.x = newx
self.y = newy
def \_\_str\_\_(self):
return "({},{})".format(self.x, self.y)
def \_\_repr\_\_(self):
return "Point({},{})".format(self.x, self.y)
\_\_init\_\_
)
rotate\_by
)
\_\_str\_\_
\_\_repr\_\_
All these functions
class
blockWe will look at them in more detail now.
The constructor is defined using the special function name \_\_init\_\_
. Its first argument is the new instance, by convention called self
, the other arguments are the arguments needed to set the instance state.
class Point():
def \_\_init\_\_(self,x,y):
self.x = x
self.y = y
The data members are set by defining an attribute of self
.
Methods that act on the object data can be defined in the class block
class Point():
# ...
def rotate\_by(self, angle):
cos = numpy.cos(angle)
sin = numpy.sin(angle)
newx = cos * self.x - sin * self.y
newy = sin * self.x + cos * self.y
self.x = newx
self.y = newy
The first argument is again self
. The object data is referred to as an attribute of self
.
class PrintDemo():
def \_\_str\_\_(self):
return "this is the \_\_str\_\_ method"
def \_\_repr\_\_(self):
return "this is the \_\_repr\_\_ method"
pd = PrintDemo()
The \_\_str\_\_
method tells python how to convert our class instances to a string. This method is used in the print
function.
print(pd)
this is the \_\_str\_\_ method
The \_\_repr\_\_
function tells python how to represent the object. It is used in error messages or when the interpreter prints the result of a statement
pd
this is the \_\_repr\_\_ method
The difference between the two is not always very clear, and sometime the inplementation is the same, the rule of thumb is:
\_\_str\_\_
should be “nice” looking\_\_repr\_\_
should have all the information needed to reconstruct the objectOne of the most useful aspect of object oriented programming is the ability to have classes inherit from other classes to specialise behaviour.
class Integrator():
def solve(self, f, x0, dt, nsteps):
xs = numpy.linspace(nsteps+1)
total = 0.0
for i in range(nsteps):
total += self.calculatePanel(f, xs[i], xs[i+1], dt)
return total
class Euler(Integrator):
def calculatePanel(self, f, xmin, xmax, dt):
return f(xmin)*dt
class Midpoint(Integrator):
def calculatePanel(self, f, xmin, xmax, dt):
m = (xmin + xmax)/2
return f(m)*dt
We can collect the common behavior in a base class (the Integrator
class) and specialise the different methods in derived classes (Euler
and Midpoint
).
There is much more about classes that we did not cover: