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]:
```

```
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]:
```

`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]:
```

`__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]:
```

`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]:
```

`*`

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]:
```

```
In [27]:
```F = Vector(2,3,4)
F

```
Out[27]:
```

```
In [28]:
```F = Vector(2,3,4)
print(F)

```
```

`__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)

```
```

`__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)

```
```

`__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 |