Object oriented programming languages like Python are powerfull because they allow you create new object types, modifiy object properites and manipulate objects with methods. An object can be resused and built upon to create new objects. So what is an "object" anyway? In Python, every instance of a class is an object.
Lets take a look at an imaginary example. Pretend your grandmother Helen is created in Python. To create a new grandma we "instantiate" or create a new object (Helen) of a class(grandmas). In this case grandma
is the name of the class, and Helen
is a specific instance of that class. Helen is a grandma, but there are a bunch of other grandmas too. Every property that a grandma
can have, our grandma Helen
can have. All of the opperations a grandma can do, our grandma Helen can do too. We need to type encolsed parenthesis ()
after the class name grandma
in order to tell Python that we want Helen to be made from the grandma
class (and not just create a variable helen and assign it whatever value is stored in the variable grandma):
# Creat a new Object Helen, a member of the grandma Class
Helen = grandma()
Once our awesome grandma Helen has been created, we could imagine our Grandma's attributes like her address. A specific attribute of a class is assigned using the dot .
notation
Helen.address = 'Portland, OR'
Maybe our grandma has a couple children, like our mom Jan and our three aunts, Ellie, Manya and Cookie.
Helen.children = ['Jan','Ellie','Manya','Cookie']
And our grandma as some grandchildren too, like me, my brother Zach and my sister Liza.
Helen.grandchildren = ['me','Zach','Liza']
Once we have assigned these properties to our grandma, we can access them using the dot notation as well.
>> Helen.address
'Portland, OR'
>> Helen.grandchildren
['me','Zach','Liza']
We can even imagine doing opperations on our grandma (not gall blatter surgury, arithmetic opperations). Suppose we create two grandmas, Helen and Margret and they each have three grandchildren:
>> Helen = grandma()
>> Helen.grandchildren = ['me','Zach','Liza']
>> Margret = grandma()
>> Margret.grandchildren = ['Sophie','Josh','Seth']
If we add our two grandmas together, we get a super grandma! In Python, we can assign specific ways that arithmetic opperations (like +, -, x, / ) are run on our grandma class. Imagine that when we add two grandmas together we get a super grandma who has all of the grandchildren of both grandmas combined:
>> SuperGrandma = Helen + Margret
>> SuperGrandma.grandchildren
['me','Zach', 'Liza', 'Sophie', 'Josh', 'Seth']
So let's create a new Grandma class in Python. In Python you create new classes by using the class
keyword followed by the class name and a colon :
. Class names are usually capitalized, so our first line will be:
class Grandma:
Then we need to indent the set of instructions which detail the properites of our Grandma class. All the definitions need to be indented. First we will define a special method __init___
. This special method is what happens when we create a new instance of a class. It __init__
method is what happens when we call Helen = Grandma()
. It is the method that runs when a new object of a class is created.
In [1]:
class Grandma:
def __init__(self):
self.address = ' '
self.children = []
self.grandchildren = []
Now we can create a new instance of the Grandma class, our grandma Helen
In [2]:
Helen = Grandma()
Let's see if Helen is part of our Grandma class using Python's type()
function.
In [3]:
type(Helen)
Out[3]:
Now we can define our grandma's attributes. These are attributes that all grandmas have. Our Grandma has Helen different values for these attributes than other grandmas. All grandmas have grandchildren (same atribute), but each grandma has different grandchildren (different values).
In [3]:
Helen.address ='Portland, OR'
Helen.children = ['Jan','Holly','Manya','Cookie']
Helen.grandchildren = ['me', 'Zach','Liza']
Now let's access these attributes using the dot .
notation. The general syntax is Object.property
In [4]:
Helen.address
Out[4]:
In [5]:
Helen.children
Out[5]:
In [6]:
Helen.grandchildren
Out[6]:
Next let's figure out a way to add new grandchildren. Imagine we have a new baby cousin who's name is Gabby. Let's create a method for our Grandma
class which adds another name in the list of grandchildren.
In [8]:
class Grandma:
def __init__(self):
self.address = ' '
self.children = []
self.grandchildren = []
def add_grandchild(self,new_grandchild):
current_grandchildren = self.grandchildren
self.granchildren = current_grandchildren.append(new_grandchild)
return self
In [9]:
Helen = Grandma()
Helen.address ='Portland, OR'
Helen.children = ['Jan','Holly','Manya','Cookie']
Helen.grandchildren = ['me', 'Zach','Liza']
In [10]:
Helen.add_grandchild('Gabby')
Helen.grandchildren
Out[10]:
Let's now add one more method to our Grandma class. Let's write a method that produces a super grandma when two grandmas are added together. To do this we need to define the __add___
method. The ___add___
method runs when we use the plus sign +
used, as in:
solution = 2 + 2
In the case of our Grandma class, we want to combine the grandchildren of both grandmas to create a new super grandma
supergrandma = Helen + Margret
We do this by defining the __add__
method within our Grandma class
In [11]:
class Grandma:
def __init__(self): # what happens when you create a new Grandma
self.address = ' '
self.children = []
self.grandchildren = []
def add_grandchild(self,new_grandchild):
current_grandchildren = self.grandchildren
self.granchildren = current_grandchildren.append(new_grandchild)
return self
def __add__(self,other_grandma): # what happens when you + two Grandma's
super_gran = Grandma()
super_gran.grandchildren = self.grandchildren + other_grandma.grandchildren
return super_gran
In [12]:
Helen = Grandma()
Margret = Grandma()
Helen.grandchildren = ['me', 'Zach','Liza']
Margret.grandchildren = ['Nichole','Mikka','Ari']
In [13]:
SuperGran = Helen + Margret
SuperGran.grandchildren
Out[13]:
I love grandma's, especially Helen and Margret, but how can we use Python classes in engineering? One type of class that would be especiall useful would be a Vector
class. Our Vector
class would have a couple properties: A magnitude, an x-component, a y-compenent, and a z-component. Also some Vectors are unit vectors, which have a magnitude of 1. Other Vectors are not unit vectors and have a magnitude that is anything else besides 1. It would be nice to have a method to quickly figure out whether a given vector is a unit vector or not.
To create our new Vector class. We need to use the class
keyword followed by the name of our class, Vector
and a colon :
class Vector:
The first method we need to define is the special __init__
method that runs when a new Vector
is created. When we create a new Vector, we need to define the i, j and k components. We'll make each of these components a different property of each Vector.
In [14]:
class Vector:
def __init__(self, i, j, k):
self.i = i
self.j = j
self.k = k
In [15]:
F = Vector(3,4,5)
F.i, F.j, F.k
Out[15]:
I'd like each of the components be a floating point number rather than an integer. We can ensure this as part of our __init__
method. It would also be good to be able to create a vector with two components, just i and j, in case we are working in two dimmesions. To do this we will set the k value to zero by default. If the user doen't list a k value when they create a new vector, the k value will be set to zero.
In [16]:
class Vector:
def __init__(self, i, j, k=0.0):
self.i = float(i)
self.j = float(j)
self.k = float(k)
In [17]:
F = Vector(3,4)
F.i, F.j, F.k
Out[17]:
Each Vector
should also have a magnitude. The magnitude of a vector is the square root of the sum of the squares of the vector's components.
Remember that to make exponents in Python, you need to use the double asterix **
not the carrot ^
symbol. We also need to import the sqrt()
function from Python's math module. sqrt() is part of the standard library of Python functions, but it still needs to be imported.
In [18]:
from math import sqrt
class Vector:
def __init__(self, i, j, k=0.0):
self.i = float(i)
self.j = float(j)
self.k = float(k)
self.mag = sqrt(self.i**2 + self.j**2 + self.k**2)
In [19]:
F = Vector(3,4)
F.mag
Out[19]:
Now let's make a method that will tell us wheather our Vector is a unit vector or not. If our vector is a unit vector, running the method on our vector object will output true
, If our vector is not a unit vector, running the method on our vector object will output false
. Our vector object is considered a unit vector if it has a magnitude = 1. Because our vector magnitude is going to be a floating point number, we need to be careful with comparison statements. If there is just a little floating point arithmetic error, then the statment mag == 1
may not return true
, even if mathmatically the magnitude should be 1. Because of this we will use an upper and lower bound of about 12 significant figures. If the magnitude is within 12 significant figures of 1 we'll call it 1.
In [20]:
from math import sqrt
class Vector:
def __init__(self, i, j, k=0.0):
self.i = float(i)
self.j = float(j)
self.k = float(k)
self.mag = sqrt(self.i**2 + self.j**2 + self.k**2)
def is_unit_vector(self):
if self.mag < 1.00000000001 and self.mag > 0.999999999999:
return True
else:
return False
In [21]:
from math import sqrt, sin, cos, pi
F = Vector(cos(pi/4),sin(pi/4))
print(F.mag)
F.is_unit_vector()
Out[21]:
Great! What are some other things we would like to be able to do with our Vectors? Adding and subtracting vectors is one of them. To add and vectors, we just sum up the components of each. To subtract vectors, we subtract the components of one from the components of the other.
$$ \vec{P} + \vec{Q} = ( P_x + Q_x )\hat{i} + ( P_y + Q_y )\hat{j} + ( P_z + Q_z )\hat{k} $$$$ \vec{P} - \vec{Q} = ( P_x - Q_x )\hat{i} + ( P_y - Q_y )\hat{j} + ( P_z - Q_z )\hat{k} $$To be able to do this type of Vector addition (use the + symbol) with our Vector objects, we need to modify the __add__
method. This will allow us to access the functionality of the plus +
sign.
To use the subtraction sign ( - ) with our Vector objects we need to modify the __sub__
method. This will allow us to access the functionality of the minus -
sign.
In [22]:
from math import sqrt
class Vector:
def __init__(self, i, j, k=0.0):
self.i = float(i)
self.j = float(j)
self.k = float(k)
self.mag = sqrt(self.i**2 + self.j**2 + self.k**2)
def is_unit_vector(self):
if self.mag < 1.00000000001 and self.mag > 0.999999999999:
return True
else:
return False
def __add__(self, other): # Vector subtraction: what happens when self + other?
return Vector(self.i + other.i, self.j + other.j, self.k + other.k)
def __sub__(self, other): # Vector addition: what happens when self - other?
return Vector(self.i - other.i, self.j - other.j, self.k - other.k)
In [23]:
P = Vector(3,4,5)
Q = Vector(1,2,3)
R = P + Q
R.i, R.j, R.k
Out[23]:
Now let's create the ability for our Vectors to be multiplied by scalars and divided by scalars. We want to have access to the times symbol *
and the division symbol /
. These are defined by the mul and truediv methods.
In [24]:
from math import sqrt
class Vector:
def __init__(self, i, j, k=0.0):
self.i = float(i)
self.j = float(j)
self.k = float(k)
self.mag = sqrt(self.i**2 + self.j**2 + self.k**2)
def is_unit_vector(self):
if self.mag < 1.00000000001 and self.mag > 0.999999999999:
return True
else:
return False
def __add__(self, other): # Vector subtraction: what happens when self + other?
return Vector(self.i + other.i, self.j + other.j, self.k + other.k)
def __sub__(self, other): # Vector addition: what happens when self - other?
return Vector(self.i - other.i, self.j - other.j, self.k - other.k)
def __mul__(self, other): # Scalar multiplication: what happens when self * other?
return Vector(self.i*other, self.j*other, self.k*other)
def __truediv__(self, other): # Scalar division: what happens when self / other?
return Vector(self.i/other, self.j/other, self.k/other)
In [25]:
F = Vector(2,3,4)
G = F*3
G.i,G.j,G.k
Out[25]:
In [26]:
F = Vector(2,3,4)
G = F/2
G.i, G.j, G.k
Out[26]:
Now let's add something special to our class so that when we type just a Vector object into the Python Interpreter, or directly into a code cell in a Jupyter Notebook, the output actually tells us something. Right now, the output isn't very useful. Printing out the Vector doesn't help. We still just get the output that our variable is part of the Vector class.
In [27]:
F = Vector(2,3,4)
F
Out[27]:
In [28]:
F = Vector(2,3,4)
print(F)
To change this behavior we need to modifiy the __str__
and ___repr__
methods. Let's define each of them to output the vector is i,j,k form:
In [29]:
from math import sqrt
class Vector:
def __init__(self, i, j, k=0.0):
self.i = float(i)
self.j = float(j)
self.k = float(k)
self.mag = sqrt(self.i**2 + self.j**2 + self.k**2)
def is_unit_vector(self):
if self.mag < 1.00000000001 and self.mag > 0.999999999999:
return True
else:
return False
def __add__(self, other): # Vector subtraction: what happens when self + other?
return Vector(self.i + other.i, self.j + other.j, self.k + other.k)
def __sub__(self, other): # Vector addition: what happens when self - other?
return Vector(self.i - other.i, self.j - other.j, self.k - other.k)
def __mul__(self, other): # Scalar multiplication: what happens when self * other?
return Vector(self.i*other, self.j*other, self.k*other)
def __truediv__(self, other): # Scalar division: what happens when self / other?
return Vector(self.i/other, self.j/other, self.k/other)
def __str__(self):
return('{}i + {}j + {}k'.format(self.i, self.j, self.k))
def __repr__(self):
return('{}i + {}j + {}k'.format(self.i ,self.j, self.k))
In [30]:
F = Vector(2,3,4)
print(F)
F
Out[30]:
Now we are going to modify our Vector class so that we can iterate through the different term, just like you can iterate through a list. We want to be able to do an opperation like:
for comp in F:
print(comp)
Right now if we try that, we are returned an error:
In [31]:
F = Vector(2,3,4)
for comp in F:
print(F)
To make our Vector class iterable, we need to define two methods, the __next___
method and the __iter__
method. The __iter__
method will simply return back the Vector object, but the __next__
method has to tell the iterator which property to access next. We are just going to cycle through the i, j, and k component properites and not include the magnitudue. To do this, we need to insert a property counter when the Vector object is created that is set to zero. This will allow the __next__
method to have a counter to cycle through.
In [32]:
from math import sqrt
class Vector:
def __init__(self, i, j, k=0.0):
self.i = float(i)
self.j = float(j)
self.k = float(k)
self.mag = sqrt(self.i**2 + self.j**2 + self.k**2)
self.term = 0
def is_unit_vector(self):
if self.mag < 1.00000000001 and self.mag > 0.999999999999:
return True
else:
return False
def __add__(self, other): # Vector subtraction: what happens when self + other?
return Vector(self.i + other.i, self.j + other.j, self.k + other.k)
def __sub__(self, other): # Vector addition: what happens when self - other?
return Vector(self.i - other.i, self.j - other.j, self.k - other.k)
def __mul__(self, other): # Scalar multiplication: what happens when self * other?
return Vector(self.i*other, self.j*other, self.k*other)
def __truediv__(self, other): # Scalar division: what happens when self / other?
return Vector(self.i/other, self.j/other, self.k/other)
def __str__(self):
return('{}i + {}j + {}k'.format(self.i, self.j, self.k))
def __repr__(self):
return('{}i + {}j + {}k'.format(self.i ,self.j, self.k))
def __next__(self):
if self.term >= 3:
raise StopIteration
if self.term == 0:
term = self.i
if self.term == 1:
term = self.j
if self.term == 2:
term = self.k
self.term += 1
return term
def __iter__(self):
return self
In [33]:
F = Vector(2,3,4)
for comp in F:
print(comp)
Lastly, we are going to add the ability to compare two vectors and see if they are equivalent. To do this we are going to define the __eq__`` and ``__ne__
methods.
In [34]:
from math import sqrt
class Vector:
def __init__(self, i, j, k=0.0):
self.i = float(i)
self.j = float(j)
self.k = float(k)
self.mag = sqrt(self.i**2 + self.j**2 + self.k**2)
self.term = 0
def is_unit_vector(self):
if self.mag < 1.00000000001 and self.mag > 0.999999999999:
return True
else:
return False
def __add__(self, other): # Vector subtraction: what happens when self + other?
return Vector(self.i + other.i, self.j + other.j, self.k + other.k)
def __sub__(self, other): # Vector addition: what happens when self - other?
return Vector(self.i - other.i, self.j - other.j, self.k - other.k)
def __mul__(self, other): # Scalar multiplication: what happens when self * other?
return Vector(self.i*other, self.j*other, self.k*other)
def __truediv__(self, other): # Scalar division: what happens when self / other?
return Vector(self.i/other, self.j/other, self.k/other)
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.__dict__ == other.__dict__
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
def __str__(self):
return('{}i + {}j + {}k'.format(self.i, self.j, self.k))
def __repr__(self):
return('{}i + {}j + {}k'.format(self.i ,self.j, self.k))
def __next__(self):
if self.term >= 3:
raise StopIteration
if self.term == 0:
term = self.i
if self.term == 1:
term = self.j
if self.term == 2:
term = self.k
self.term += 1
return term
def __iter__(self):
return self
In [35]:
K = Vector(1,1,1)
H = Vector(1,1,1)
K == H
Out[35]:
In [36]:
K = Vector(1,1,1)
H = Vector(0,1,1)
K == H
Out[36]:
In [37]:
K = Vector(1,1,1)
H = Vector(0,1,1)
L = Vector(1,0,0)
R = H + L
R == K
Out[37]:
I hope from the two examples above, you have an idea of how to create a new Class in Python. Designing your own classes can be very useful when solving engineering problems with Python.
The chart below details the methods used in the Vector Class
Method | Operation | Description |
---|---|---|
__init__ |
object = Class() |
create a new object of the Class |
__add__ |
+ |
addition |
__sub___ |
- |
subtraction |
__mul___ |
* |
multiplication |
__truediv___ |
/ |
division |
__eq___ |
== |
equivalent |
__ne___ |
!= |
not equivalent |
__str___ |
print(object) |
use the print() function |
__repr___ |
object |
print in the REPL or notebook |
__iter___ |
for a in object |
iteration |
__next___ |
iteration order |