Lecture 14 - Classes

We could spend a lof time discussing classes and object oriented programming but we only care about the basics in this course. It's important to know the main idea and be able to write simple classes that serve basic mathematical purposes.

This will explain expressions like xs.append(5) that we used for lists.

The main idea of classes is that sometimes you want to put various pieces of data, and functions that manipulate the data in one place. The functions are called methods of the class.

For example, a vector in $\mathbb{R}^3$ is represented by three floats. So why not make a new data type that contains three floats and call it a vector?

Actually, let's make a list of all the things we know we can do to vectors in $\mathbb{R}^3$. We can:

  • Have x, y, z variables for the three coordinates,
  • take the length of a vector (a.k.a. norm),
  • we can normalize the vector, meaning we can divide it by the norm so that it now has the same direction but has length 1,
  • take dot product of two vectors, i.e. take $(x_1,y_1,z_1)\cdot(x_2,y_2,z_2) = x_1x2 + y_1y_2 + z_1z_2$,
  • add vectors,
  • multiply a vector by a scalar
  • take cross product of two vectors

We will make a new type of object in Python, called Vector, that allows us to do all of these things.

In [1]:
import math
class Vector():
    
    # initialize the vector, we will say   
    # v = Vector(1,2,3)
    # to get a new vector with those coordinated
    def __init__(self, xx, yy, zz):
        self.x = xx
        self.y = yy
        self.z = zz
    
    # compute the norm
    def norm(self):
        return math.sqrt(self.x*self.x + self.y*self.y + self.z*self.z)

    # divide by the norm
    def normalize(self):
        no = self.norm()
        self.x /= no
        self.y /= no
        self.z /= no    
        
# note that every function has self in it, so that it can access the information of the class

The class specifies what constitutes an object (the variables in it, which are all the variables that appear as self.---), and the functions that can be used. To create an object of the class, a.k.a. an instance of the class, we need to call the name of the class as if it's a function.

In [2]:
v = Vector(1.0,2.0,3.0)   
# the arguments of this call must match the arguments of the __init__ 
# function in the class, except the self part
In [3]:
type(v)
Out[3]:
__main__.Vector
In [4]:
print(v.x)
print(v.y)
print(v.z)
1.0
2.0
3.0
In [5]:
v.normalize()
print(v.x, v.y, v.z)
0.2672612419124244 0.5345224838248488 0.8017837257372732
In [6]:
print(v)
<__main__.Vector object at 0x10ba78748>

Python doesn't yet know how to make a string from our class, so that it can print it. We do that with the __repr__(self) function

In [7]:
import math
class Vector():
    
    def __init__(self, xx, yy, zz):
        self.x = xx
        self.y = yy
        self.z = zz
    
    def norm(self):
        return math.sqrt(self.x*self.x + self.y*self.y + self.z*self.z)

    def normalize(self):
        no = self.norm()
        self.x /= no
        self.y /= no
        self.z /= no    
    
    def __repr__(self):
        return str(self.x) + "," + str(self.y) + "," + str(self.z)
    
# note that every function has self in it, so that it can access the information of the class
In [8]:
v = Vector(1.0,2.0,3.0)
In [9]:
print(v)
1.0,2.0,3.0

This should remind of how we used xs.appen(5) for a list xs. Indeed, list is a class just like our Vector class, and there is a function called append(self, x) in it that adds an extra element.

Let's also add dot products, scalar multiplication:

In [10]:
import math
class Vector():
    
    def __init__(self, xx, yy, zz):
        self.x = xx
        self.y = yy
        self.z = zz
    
    def norm(self):
        return math.sqrt(self.x*self.x + self.y*self.y + self.z*self.z)

    def normalize(self):
        no = self.norm()
        self.x /= no
        self.y /= no
        self.z /= no    
    
    def __repr__(self):
        return str(self.x) + "," + str(self.y) + "," + str(self.z)
    
    def dot(self, w):
        return self.x * w.x + self.y * w.y + self.z * w.z
    
    # returns a new vector without modifying the original
    def times_scalar(self, alpha):
        return Vector(alpha * self.x, alpha * self.y, alpha * self.z)
        
# note that every function has self in it, so that it can access the information of the class
In [11]:
Vector(1.0,2.0,3.0).dot(Vector(1.0,1.0,1.0))
Out[11]:
6.0
In [12]:
Vector(1.0,2.0,3.0).times_scalar(3)
Out[12]:
3.0,6.0,9.0

We can also write addition of vectors, we could write add(self,w) in the class and use v.add(w) to add v and w, but we could also overload the + operator as follows:

In [13]:
import math
class Vector():
    
    def __init__(self, xx, yy, zz):
        self.x = xx
        self.y = yy
        self.z = zz
    
    def norm(self):
        return math.sqrt(self.x*self.x + self.y*self.y + self.z*self.z)

    def normalize(self):
        no = self.norm()
        self.x /= no
        self.y /= no
        self.z /= no    
    
    def __repr__(self):
        return str(self.x) + "," + str(self.y) + "," + str(self.z)
    
    def dot(self, w):
        return self.x * w.x + self.y * w.y + self.z * w.z
    
    def __add__(self, w):
        return Vector(self.x + w.x, self.y + w.y, self.z + w.z)
    
# note that every function has self in it, so that it can access the information of the class
In [14]:
(Vector(1.0, 2.0, 3.0) + Vector(0.0, 5.0, 10.0))
Out[14]:
1.0,7.0,13.0

Good exercise:

Make a class called Line which represents a line with equation $ax + by + c = 0$. Write the __init__(self,a,b,c), __repr__(self) and intersect(self,other_line) methods for the class. The intersect method should return the coordinates of the intersection point of two lines.