Modules and Objects#

In the last part we have learned about functions and how to use them. Now we first learn how to write our own modules and how to import them. Then we will learn about objects and classes.

Modules#

When defining functions in the code we want to use them in, it can get really crowded and hard to read. Therefore, we can define functions in separate files and import them into our code. Furthermore, we can reuse these modules and import our stuff in other projects and scripts as well. These files are called modules. NumPy, for example, is a module that contains many functions and classes for numerical computing. You already know how to import it:

import numpy as np

After that you can use functions and classes from the module by using the dot operator:

array1 = np.array([1, 2, 3])

Writing your own modules#

A module is simply put another Python file. You can write your own modules by creating a new Python file and defining functions in it. Let’s create a new file called module1.py and define a function in it:

def my_function():
    print("I was imported from module1")

Now we can import this module (saved as module1.py) and use the function my_function:

import module1
module1.my_function()
I was imported from module1

Note that the file module1.py has to be in the same directory as the script you are running. If you want to import a module from a different directory, you can add the directory to the Python path. This can be done by adding the following lines to your script:

import sys #this is a standard Python module to access system functions
sys.path.append("path/to/your/module")
import module1

Or you can copy your module to the Python path. You can find out what the Python path is by running the following code:

import sys
print(sys.path) # this might look weird but it is a list of directories where Python looks for modules

After receiving the path, you can copy your module to one of the directories listed in the output and then use it where ever you have this interpreter running.

Objects and Classes#

Remember NumPy arrays? They are objects. Objects are a way to bundle data and functions that operate on that data. For example, a NumPy array contains data and functions to manipulate this data. A class is a blueprint for an object. It defines what data an object of this class has and what functions can be used on this data. Let’s define a class:

class Class1:
    def __init__(self, x):
        self.x = x
        
    def my_function(self):
        print("I was called with x =", self.x)

There are several things to unpack here, so let’s go through it step by step:

  • class Class1:: This line defines a new class called Class1.

  • def __init__(self, x): This is a special function called a constructor. It is called when an object of this class is created. The self parameter is a reference to the object itself and needs to be there. The x parameter is the data we want to store in the object.

  • self.x = x: This line stores the data x in the object. We cannot give the data a name like x directly, because it would be lost when the constructor finishes. Instead, we store it in the object using self.x.

  • def my_function(self): This is a function that can be called on an object of this class. The self parameter is again a reference to the object itself.

  • print("I was called with x =", self.x): This line prints the data stored in the object.

Creating an object#

Now that we have defined a class, we can create an object of this class:

object1 = Class1(5)

This line defines a new object of the class Class1 and calls the constructor with the parameter 5. The data 5 is stored in the object. But what happens if we call the function my_function on this object?

object1.my_function()
I was called with x = 5

As you would expect, the function prints the data stored in the object. We can create multiple objects of the same class and they will all have their own data. This really shows the usefulness of objects and classes. They allow us to bundle data and functions that operate on this data together and lets us have multiple instances of this bundle.

object2 = Class1(10)

As you can see, both are not interfering with each other. They have their own data and functions.

object2.my_function()
object1.my_function()
I was called with x = 10
I was called with x = 5

We can also change the content of the object, by changing the data stored in it:

object1.x = 7

This line changes the data stored in the object object1 to 7. If we call the function my_function on object1 now, it will print the new data:

object1.my_function()
I was called with x = 7

Also, we can change data in the object by calling a function on it:

class Class2:
    def __init__(self, x):
        self.x = x
        
    def set_x(self, new_x):
        self.x = new_x
        
    def my_function(self):
        print("I was called with x =", self.x)

This is a new class Class2 that has a function set_x that changes the data stored in the object. Let’s create an object of this class and change the data stored in it:

object3 = Class2(3)
object3.my_function()
I was called with x = 3

Now we change the data stored in the object:

object3.set_x(8)
object3.my_function()
I was called with x = 8

As you can see, the data stored in the object has changed. This is a very powerful concept and is used in many programming languages. This is the basis of object-oriented programming.

Putting it all together#

We can of course use modules and classes together. Let’s define a class in a module and import it into our script. For this create a new file called module2.py and define a class in it:

class ModuleClass:
    def __init__(self, x):
        self.x = x
        
    def my_function(self):
        print("I was called with x =", self.x)

Now we can import this class and use it in our script:

import module2 as m2 # we can of course give our module an alias
object4 = m2.ModuleClass(4)

And we can call the function on the object:

object4.my_function()
I was called with x = 4