Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Lecture 01: Python primer

University of Lausanne

Course: Deep Learning for Solving and Estimating Dynamic Models in Economics and Finance
Script reference: Front matter and Appendix~E — classes, methods, inheritance
Notebook role: primer (pre-course self-study; skip if you write Python every day)
Author: Simon Scheidegger

Python Basics 8: Classes

The concept of class allows you to organize your code into objects that have data attributes and functionality. First, let me just mention that we have already used a class: the list. Here we are going to learn how to make our own classes. The syntax for making a class is as follows:

class ClassName(object):
    """
    A docstring that explains what the class does.
    """

    def __init__(self, arg1, arg2, ...):
        """
        A special function that initializes objects of the class.
        """
    
    def other_funcs(self, ...):
        """
        A function.
        """

I think it is easier to understand what is going on through examples. Let’s make a class representing a 3D vector:

r=xi^+yj^+zk^.\vec{r} = x\hat{i} + y\hat{j} + z\hat{k}.

Initially, it won’t do much. But we will give it some functionality as we go.

class Vector(object):
    """
    Represents a 3D vector.
    
    """
    
    def __init__(self, x, y, z):
        """
        Initializes the class.
        
        x, y, and z are the i, j, and k cordinates.
        """
        self.x = x
        self.y = y
        self.z = z

Let me first show you how the class can be used and what it does. Then I will try to explain what __init__ does:

r = Vector(0.5, 1.0, 3.0)

We have made a simple vector. Notice that we can access the x, y, z components like this:

r.x
0.5
r.y
1.0
r.z
3.0

Data that you can access like this are called class attributes. So, you can inspect the class attributes. You can also change the class attributes like this:

r.x = 2.0
r.x
2.0

Before we add more functionality, notice how the class appears when you ask for help:

help(Vector)
Help on class Vector in module __main__:

class Vector(builtins.object)
 |  Vector(x, y, z)
 |  
 |  Represents a 3D vector.
 |  
 |  Methods defined here:
 |  
 |  __init__(self, x, y, z)
 |      Initializes the class.
 |      
 |      x, y, and z are the i, j, and k cordinates.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

And here is how the class is printed:

print(r)
<__main__.Vector object at 0x7f46e845e9b0>

This is not very nice. To print something nicer, you need to add more functionality to the class. We can do this by implementing a special class function called __repr__(). Here is the new code:

class Vector(object):
    """
    Represents a 3D vector.
    
    """
    
    def __init__(self, x, y, z):
        """
        Initializes the class.
        
        x, y, and z are the i, j, and k cordinates.
        """
        self.x = x
        self.y = y
        self.z = z
        
    def __repr__(self):
        """
        Returns a string representation of the class.
        """
        return '{0:1.2f}i + {1:1.2f}j + {2:1.2f}k'.format(self.x, self.y, self.z)

Let’s recreate the vector and print it:

r = Vector(0.5, 1.0, 3.0)
print(r)
0.50i + 1.00j + 3.00k

Okay, better.

What else would you like to do with vectors? Well, you would probably like to:

  • multiply a vector with a scalar

  • add to vectors together

  • find the dot product of two vectors

  • find the magnitude of a vector

  • find the angle between two vectors

  • find the outer product between two vectors

  • normalize a vector

  • find the projection of a vector in a given direction Let’s add all this functionality to the Vector gradually so that you start get a feeling of what is going on. Let’s start with scalar multiplication.

class Vector(object):
    """
    Represents a 3D vector.
    
    """
    
    def __init__(self, x, y, z):
        """
        Initializes the class.
        
        x, y, and z are the i, j, and k cordinates.
        """
        self.x = x
        self.y = y
        self.z = z
        
    def __repr__(self):
        """
        Returns a string representation of the class.
        """
        return '{0:1.2f}i + {1:1.2f}j + {2:1.2f}k'.format(self.x, self.y, self.z)
    
    def multiply(self, mu):
        """
        Multiply self with the scalar mu.
        
        Returns a new vector.
        """
        # Make sure mu is a float
        assert isinstance(mu, float)
        return Vector(mu * self.x, mu * self.y, mu * self.z)

Let’s try it out:

r1 = Vector(0.5, 1.0, 3.0)
r2 = r1.multiply(2.0)
print('r1 = ', r1)
print('r2 = 2 * r1 = ', r2)
r1 =  0.50i + 1.00j + 3.00k
r2 = 2 * r1 =  1.00i + 2.00j + 6.00k

Now, let’s explain what is going on inside the class function multiply(). First notice the definition of the function:

    def multiply(self, mu):

The self argument is a special argument that is required for all class functions. It is a short of handle that an instance of the class object can use to refer to itself. Inside the body of the function we see the expression:

        return Vector(mu * self.x, mu * self.y, mu * self.z)

What this does is it creates a new Vector object with x, y, z coordinates given by the x, y, z coordinates of the object calling multiply (which is referred to by self) and it returns the newly created vector. More specifically, when we do mu * self.x we mean "multiply mu with the x that you can find in the object that is calling multiply, i.e., self.

I understand that this may not be the best possible explanation of these concepts, but we are not going to spend an entire lecture on this. Hopefully, you will pick this up through more examples and throughout the class. But even if you don’t, it’s not vitally important to be able to create your own classes in order to understand the data science content of the course. We will mostly be users of clases. Classes that implement numerical arrays, data frames, plots, data science methods. It is considerably easier to understand how to use clases. If you do want to learn more about classes, however, I suggest that you read this chapter Think Python Chapter 15.

Let’s now add more functionality to the class. I will add a dot product, the magnitude, and the cross product. Study the code below line by line.

import math

class Vector(object):
    """
    Represents a 3D vector.
    
    """
    
    def __init__(self, x, y, z):
        """
        Initializes the class.
        
        x, y, and z are the i, j, and k cordinates.
        """
        self.x = x
        self.y = y
        self.z = z
        
    def __repr__(self):
        """
        Returns a string representation of the class.
        """
        return '{0:1.2f}i + {1:1.2f}j + {2:1.2f}k'.format(self.x, self.y, self.z)
    
    def multiply(self, mu):
        """
        Multiply self with the scalar mu.
        
        Returns a new vector.
        """
        # Make sure mu is a float
        assert isinstance(mu, float)
        return Vector(mu * self.x, mu * self.y, mu * self.z)
    
    # The new parts:
    
    def dot(self, r):
        """
        Return the dot product of self with r.
        """
        return self.x * r.x + self.y * r.y + self.z * r.z
    
    def magnitude(self):
        """
        Return the magnitude of self.
        """
        # The magnitude is just the square root of the dot
        # product of self with self:
        return math.sqrt(self.dot(self))
    
    def cross(self, r):
        """
        Return the cross product of self with r.
        """
        return Vector(self.y * r.z - self.z * r.y,
                     -(self.x * r.z - self.z * r.x),
                      self.x * r.y - self.y * r.x)
    
    def normalize(self):
        """
        Returns a unit vevtor in the direction of self.
        """
        return self.multiply(1.0 / self.magnitude())
r1 = Vector(0.5, 1.0, 3.0)
r2 = Vector(2.3, 2.0, -3.0)
r1.dot(r2)
r3 = r1.cross(r2)
print(r3)
r3.dot(r1) # the cross product is orthogonal to the vectors you cross:
r1.magnitude()
u = r1.normalize()
print(u)
u.magnitude()

Questions

  • In the code block below, use the function of the class Vector to compute the angle between r1 and r2.

# Your code here
  • In the redefinition of the class Vector below, add a function to that calculates the angle between the calling vector and another vector. Remember that the formula is:

    θ=acos(r1r2r1r2).\theta = \operatorname{acos}\left(\frac{\vec{r}_1 \cdot \vec{r}_2}{|\vec{r}_1||\vec{r}_2|}\right).
class Vector(object):
    """
    Represents a 3D vector.
    
    """
    
    def __init__(self, x, y, z):
        """
        Initializes the class.
        
        x, y, and z are the i, j, and k cordinates.
        """
        self.x = x
        self.y = y
        self.z = z
        
    def __repr__(self):
        """
        Returns a string representation of the class.
        """
        return '{0:1.2f}i + {1:1.2f}j + {2:1.2f}k'.format(self.x, self.y, self.z)
    
    def multiply(self, mu):
        """
        Multiply self with the scalar mu.
        
        Returns a new vector.
        """
        # Make sure mu is a float
        assert isinstance(mu, float)
        return Vector(mu * self.x, mu * self.y, mu * self.z)
    
    # The new parts:
    
    def dot(self, r):
        """
        Return the dot product of self with r.
        """
        return self.x * r.x + self.y * r.y + self.z * r.z
    
    def magnitude(self):
        """
        Return the magnitude of self.
        """
        # The magnitude is just the square root of the dot
        # product of self with self:
        return math.sqrt(self.dot(self))
    
    def cross(self, r):
        """
        Return the cross product of self with r.
        """
        return Vector(self.y * r.z - self.z * r.y,
                     -(self.x * r.z - self.z * r.x),
                      self.x * r.y - self.y * r.x)
    
    def normalize(self):
        """
        Returns a unit vevtor in the direction of self.
        """
        return self.multiply(1.0 / self.magnitude())
    
    # Your code here:
    def angle(self, r):
        """
        Return the angle (in radians) between self and r.
        """
        # Your code here
# Test your code here:
r1 = Vector(0.5, 1.0, 3.0)
r2 = Vector(2.3, 2.0, -3.0)
r1.angle(r2)
  • Now that you wrote your first function with a bit of help, see if you can write one more on your own. Create a function that gives you the projection of a vector in the direction of self. Say the self corresponds to the vector r1\vec{r}_1 and the other vector is r2\vec{r}_2. The formula that gives you the projection is:

    Prr1(r2):=(r1r2)r1r1.\operatorname{Pr}_{\vec{r}_1}(\vec{r}_2) := \left(\vec{r}_1\cdot \vec{r_2}\right)\frac{\vec{r}_1}{|\vec{r}_1|}.

    Call this function project.

# Copy-paste and add functionality to the Vector class here
# Try your code here
r1 = Vector(0.5, 1.0, 3.0)
r2 = Vector(2.3, 2.0, -3.0)
r1.project(r2)