Object oriented programming is a way of thinking about and defining how different pieces of software and ideas work together. In objected oriented programming, there are two main interfaces: classes and objects.
Unlike functional or procedural paradigms, there are three main features that classes provide.
Inheritence: A class may automatically gain all of the attributes and methods from another class it is related to. The new class is called a subclass or sometimes a subtype. Multiple levels of inheritance sets up a class heirarchy. For example:
Polymorphism: Subclasses may override methods and attributes of their parents in a way that suitable to them. For example:.
If this seems more complicated than writing functions and calling them in sequence that is because it is! However, obeject orientation enables authors to cleanly separate out ideas into independent classes. It is also good to know because in many languages - Python included - it is the way that you modify the type system.
Object oriented programming revolves around the creation and manipulation of objects that have attributes and can do things. They can be as simple as a coordinate with x and y values or as complicated as a dynamic webpage framework. Here is the code for making a very simple class that sets an attribute.
In [ ]:
class MyClass(object):
def me(self, name):
self.name = name
In [ ]:
my_object = MyClass()
In [ ]:
type(my_object)
In [ ]:
my_object.name = 'Anthony'
print my_object.name
In [ ]:
my_object.me('Jan')
print my_object.name
In the object oriented terminology above:
object
- a special class which should be the parent of all classes.You write a class and you create and object.
Hands on Example
Write an Atom class with mass and velocity attributes and an energy() method.
In [ ]:
Here is a more complex and realisitic example of a matrix class:
In [ ]:
# Matrix defines a real, 2-d matrix.
class Matrix(object):
# I am a matrix of real numbers
def __init__(self, h, w):
self._nrows = h
self._ncols = w
self._data = [0] * (self._nrows * self._ncols)
def __str__(self):
return "Matrix: " + str(self._nrows) + " by " + str(self._ncols)
def setnrows(self, w):
self._nrows = w
self.reinit()
def getnrows(self):
return self._nrows
def getncols(self):
return self._ncols
def reinit(self):
self._data = [0] * (self._nrows * self._ncols)
def setncols(self, h):
self._ncols = h
self.reinit()
def setvalue(self, i, j, value):
if i < self._nrows and j < self._ncols:
self._data[i * self._nrows + j] = value
else:
raise Exception("Out of range")
def multiply(self, other):
# Perform matrix multiplication and return a new matrix.
# The new matrix is on the left.
result = Matrix(self._nrows, other.getncols())
# Do multiplication...
return result
def inv(self):
# Invert matrix
if self._ncols != self._nrows:
raise Exception("Only square matrices are invertible")
inverted = Matrix(self._ncols, self._nrows)
inverted.setncols(self._ncols)
inverted.setnrows(self._ncols)
return inverted
In [ ]:
class A(object):
a = 1
In [ ]:
avar = A()
getattr(avar, 'a')
In [ ]:
setattr(avar, 'q', 'mon')
print avar.q
Users shouldn't have to know how your program works in order to use it.
The interface is a contract saying what a class knows how to do. The code above defines matrix multiplication, which means that mat1.multiply(mat2) should always return the right answer. It turns out there are many ways to multiply matrices, and there are whole Ph.Ds written on performing efficient matrix inversion. The implementation is the way in which the contract is carried out.
In [ ]:
class Person(object):
def __init__(self):
self.name = "Anthony"
In [ ]:
person = Person()
print person.name
Constructors may take arguements just like any other method or function.
In [ ]:
class Person(object):
def __init__(self, name, title="The Best"):
self.name = name
self.title = title
In [ ]:
anthony = Person("Anthony")
print anthony.name, anthony.title
In [ ]:
jan = Person("Jan", "The Greatest")
print jan.name, jan.title
If you want a to create a class that behaves mostly like another class, you should not have to copy code. What you do is subclass and change the things that need changing. When we created classes we were already subclassing the built in python class "object."
For example, let's say you want to write a sparse matrix class, which means that you don't explicitly store zero elements. You can create a subclass of the Matrix class that redefines the matrix operations.
In [ ]:
class SparseMatrix(Matrix):
# I am a matrix of real numbers
def __str__(self):
return "SparseMatrix: " + str(self._nrows) + " by " + str(self._ncols)
def reinit(self):
self._data = {}
def setValue(self, i, j, value):
self._data[(i,j)] = value
def multiply(self, other):
# Perform matrix multiplication and return a new matrix.
# The new matrix is on the left.
result = SparseMatrix(self._nrows, other.getncols())
# Do multiplication...
return result
def inv(self):
# Invert matrix
if self._nrows != self._rcols:
raise Exception("Only square matrices are invertible")
inverted = SparseMatrix(self._ncols, self._nrows)
The SparseMatrix object is a Matrix but some methods are defined in the superclass Matrix. You can see this by looking at the dir of the SparseMatrix and noting that it gets attributes from Matrix.
In [ ]:
dir(SparseMatrix)
A more minimal and more abstact version of inheritence may be seen here:
In [ ]:
class A(object):
a = 1
class B(A):
b = 2
class C(B):
b = 42
c = 3
x = C()
print x.a, x.b, x.c
Normally, when you get or set attributes on an object the value that you are setting simply gets a new name. However, sometimes you run into the case where you want to do something extra depending on the actual value you are reciveing. For example, maybe you need to confirm that the value is actually correct or desired.
Python provides a mechanism called properties to do this. Properties are methods which either get, set, or delete a given attribute. To implement this, use the built-in property()
decorator:
In [ ]:
class EvenNum(object):
def __init__(self, value):
self._value = 0
self.value = value
@property
def value(self):
# getter
return self._value
@value.setter
def value(self, val):
# setter
if val % 2 == 0:
self._value = val
else:
print "number not even"
In [ ]:
en = EvenNum(42)
print en.value
In [ ]:
en.value = 65
print en.value
In [ ]:
en.value = 16
print en.value
In [ ]:
x = EvenNum(42)
y = 65
x + y
We need to let Python know that EvenNum addition should be addition on the value. We should also let it know that it should return an EvenNum if it can.
To do this we have to implmenet part of the Python Data Model. Python has a list of special - or sometimes known as magic - method names that you can override to implement support for many language operations. All of these method names start and end with a double underscore __
. This is because no regual method would ever use such an obtuse name. It also lets the user and other developers know that something special is happening in those methods and that they aren't meant to be called directly. Many of these has a predefined interface they must follow.
We have already seen an example of this with the __init__()
constructor method. Now let's try to make addition work for EvenNum. From the documentation, there is an __add__()
method with the following API:
object.__add__(self, other)
In [ ]:
class EvenNum(object):
def __init__(self, value):
self._value = 0
self.value = value
@property
def value(self):
# getter
return self._value
@value.setter
def value(self, val):
# setter
if val % 2 == 0:
self._value = val
else:
print "number not even"
def __add__(self, other):
if isinstance(other, EvenNum):
newval = self.value + other.value
else:
newval = self.value + other
return EvenNum(newval)
In [ ]:
x = EvenNum(42)
y = 65
x + y
One of the most useful of these special methods is the __str__()
method, which allows you to provide a string representation of the object.
In [ ]:
class EvenNum(object):
def __init__(self, value):
self._value = 0
self.value = value
def __str__(self):
return str(self.val)
@property
def value(self):
# getter
return self._value
@value.setter
def value(self, val):
# setter
if val % 2 == 0:
self._value = val
else:
print "number not even"
def __add__(self, other):
if isinstance(other, EvenNum):
newval = self.value + other.value
else:
newval = self.value + other
return EvenNum(newval)
In [ ]:
x = EvenNum(42)
y = 16
print x + y
Lastly, the most important magic part of classes and objects is the __dict__
attribute. This is dictionary where all of the the method and attributes of an object are stored. Modifying the __dict__
directly affects the object and vice versa.
In [ ]:
class A(object):
def __init__(self, a):
self.a = a
avar = A(42)
In [ ]:
print avar.__dict__
In [ ]:
avar.__dict__['b'] = 'yourself'
print avar.b
In [ ]:
avar.c = "me now"
print avar.__dict__
It is because of this that dictionaries are the most important container in Python. Under the covers, all types ae just dicts.