17.6. Importing Our Own Modules

On the previous page, we imported the turtle module into our turtle_fun.py program. We can do something similar with any of the ready-made Python modules.

1
2
3
import turtle
import string
from random import randint

We just need to know the names of the modules we want. We do not need to tell VS Code where the files are stored. Since they are installed with Python, the application knows where to find them.

If we code our own modules, like we did in the Creating Modules section, then we must be a little more careful with the import syntax.

17.6.1. Setup

In VS Code, do the following:

  1. Create a new directory called module_practice.

  2. Inside module_practice, create a new file called main.py.

  3. Also in module_practice, create a file called list_filler.py.

Our file tree should look something like this:

File tree of the local_practice directory showing the contents of the module_practice subdirectory.

The local_practice directory tree.

Now paste this code into main.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Keep line 1 empty for now.

def main():
   entries = 10
   range_start = 15
   range_end = 75

   for turn in range(5):
      numbers = list_filler.unique_num_list(entries, range_start, range_end)
      numbers.sort()
      print(f"{turn+1}) Numbers list = {numbers}")

if __name__ == '__main__':
   main()

Line 9 calls the function unique_num_list, but it’s not defined in the main.py code. We’ll fix this in a moment.

Paste the following statements into list_filler.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import random

# Generate a list of random numbers in the range start-end.
# Repeats of the same number are possible.
def make_num_list(num_entries, start = 1, end = 10):
   num_list = []
   for num in range(num_entries):
      num_list.append(random.randint(start, end))

   return num_list

# Generate a list of random numbers in the range start-end.
# No repeated numbers occur in the list.
def unique_num_list(num_entries, start = 1, end = 100):
   num_list = []
   if num_entries > (end - start + 1):
      num_entries = end - start + 1
   while len(num_list) < num_entries:
      new_num = random.randint(start, end)
      if new_num not in num_list:
         num_list.append(new_num)

   return num_list

Now we are getting somewhere! The unique_num_list function is defined in lines 14 - 23. The next step is to import list_filler.py into main.py.

17.6.2. Import From the Same Directory

When we create a module, we save the code into its own .py file. The most convenient way to import our new module is to put that file in the same directory as our main program. Fortunately, this is exactly what we did in the Setup section.

File tree of the local_practice directory showing the contents of the module_practice subdirectory.

The local_practice directory tree.

When we follow this structure, the import syntax is the same as before.

Try It!

  1. On line 1 in main.py, add the statement import list_filler.

  2. Run main.py to see the results.

    Sample Output:

    $ python main.py
    1) Numbers list = [22, 27, 33, 36, 40, 42, 45, 56, 66, 71]
    2) Numbers list = [16, 23, 39, 43, 47, 49, 51, 53, 64, 68]
    3) Numbers list = [18, 20, 26, 43, 45, 49, 52, 59, 60, 61]
    4) Numbers list = [15, 31, 36, 37, 43, 46, 52, 56, 57, 59]
    5) Numbers list = [21, 23, 28, 30, 51, 56, 58, 60, 62, 72]
    
  3. Note the use of dot-notation in line 9 of main.py. The syntax list_filler.unique_num_list calls the unique_num_list function from the list_filler module.

  4. Use a for loop to generate another five lists of random numbers. This time, however, use the make_num_list function, and only send in one argument.

Think of any program as a set of smaller tasks joined together. Instead of writing one large main.py file with hundreds or thousands of lines of code, we can split our program into smaller modules. Each one has a specific focus and purpose. The job of main.py is to pull them together and manage how their classes and functions get used.

By splitting our projects into smaller pieces, we make updating and debugging our code much easier.

With this picture in mind, it makes a lot of sense to keep the modules we need in the same directory with the main program.

17.6.3. Import From a Different Directory

If we create a large number of modules, we might want to add some subfolders to help keep the files organized. Let’s explore this with a simple example.

File tree of the local_practice/module_practice/helpers file tree.

helpers is a subdirectory of module_practice.

In this case, main.py still sits in the module_practice directory. However, we moved list_filler.py into the helpers subfolder.

Try It!

  1. In VS Code, use the New Folder button in the File Explorer to create the helpers directory inside module_practice.

  2. Drag-and-drop the list_filler.py file into the new folder.

  3. Run the program again and examine the error message.

Console Output

Traceback (most recent call last):
File "main.py", line 1, in <module>
   import list_filler
ModuleNotFoundError: No module named 'list_filler'

When the import list_filler statement runs, Python follows a specific set of steps to find the file. We won’t detail the entire process here. However, the short version is:

  1. Check if list_filler is one of the standard Python modules. If so, import it (the locations of these modules are known).

  2. Check the current directory for list_filler. If found, import it.

  3. Throw an error if list_filler can’t be found.

By moving list_filler.py into the helpers directory, Python can no longer find the file to import it! This causes the ModuleNotFoundError.

Fortunately, we can fix the import syntax and provide a path to where we stored our module. The key is to include the from keyword. The general syntax is:

from directory_name import module_name

IMPORTANT: directory_name must be in the same folder as the main program.

Try It!

  1. In main.py, change line 1 to from helpers import list_filler.

  2. Run the program again.

  3. Ta da!

17.6.3.1. Other Import Details

As long as the modules we need are stored in directories below main.py, we can extend the chain of directory names as far as necessary with our import statement. To move down multiple levels from main.py, we use dot-notation to include the different directory names.

Example

Let’s take a look at a three level file tree:

A three level file tree.

The module gradebook.py is two levels below main.py.

The indentation in the file tree gives us clues about how things are organized.

  1. main.py is in the folder my_project along with the subdir1 directory.

  2. subdir1 contains the grade_stats.py file and the next subfolder, subdir2.

  3. Finally, we see that the gradebook.py file is buried deepest in this tree.

To import the gradebook module, line 1 in main.py would be:

from subdir1.subdir2 import gradebook

We can also import a specific function (or class) from a module stored in a subdirectory. In this case, we include the module name in our dot-notation.

Example

Assume we only need the average_grades function from the grade_stats module. The syntax would be:

from subdir1.grade_stats import average_grades

This statement tells Python, Look in the subdir1 directory and find the grade_stats file, then import the average_grades function.

17.6.4. Importing From a Parent Directory

While it is possible to force Python to search upwards through your file system, this isn’t recommended. To do so requires a work-around, and that process can go wrong in lots of ways.

If the Python developers thought it would be useful to search upwards in the file system, then they would have made it easy. They didn’t, so it isn’t.

This just reinforces a GREAT idea for making local programs and projects: Keep related content together. This keeps our work organized, and it also helps any other programmer who might inherit our work.

Whenever you create a module to support your program, follow these guidelines on where to store the file:

  1. Put it in the same directory as the main program!

  2. Put it in a subdirectory below the main program.

  3. See point 1.

17.6.5. Check Your Understanding

Use the file tree shown below to answer the questions.

File tree showing /MyLaptop/Project/Helpers/Data.

Question

Which statement should we add to seating.py to import the rosters.py module?

  1. import rosters
  2. from seating import rosters
  3. from Mods import rosters
  4. from Mods.Data import rosters
  5. from Data import rosters

Question

What should we do if we want to import grade_calcs.py into rosters.py?

  1. Move both files into the same directory.
  2. Play around with the from ... import ... syntax until we get the import to work.
  3. Use Google to find out how to import from a parent directory.
  4. Cry.