Object oriented programming¶
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.
Example: rotation of a set of points¶
Let's see how it would look in both the procedural and the object oriented way
Procedural 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]]
Object oriented way¶
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 definition¶
class 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)
Ingredients of a python class¶
- the constructor (
__init__)- initialise the instance data
- methods (
rotate_by)- act on the data
- auxilliary methods
__str____repr__
All these functions
- are defined inside the
classblock - take the class instance as their first argument
We will look at them in more detail now.
Constructor¶
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.
Member functions¶
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.
Printing¶
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 object
Inheritance¶
One 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 ).
More about classes¶
There is much more about classes that we did not cover:
- destructor: some actions can be triggered when the instance of a class is deleted
- operator overloading: you can define how class instances are added, subtracted, multiplied, ...
- class methods: you can define functions that are attached to the class rather than to an instance