← Back to course

Modules and Imports: Organizing Bigger Programs

Modules and Imports: Organizing Bigger Programs

Welcome back.

In the previous lesson, you learned error handling.

Your programs learned how to survive bad input, missing files, and dramatic Python explosions.

You wrote things like:

try:
    age = int(input("Age: "))
except ValueError:
    print("Please enter a valid age.")

Very good.

Now your programs are safer.

They do not immediately faint when someone types:

banana

Excellent progress.

But now we have another problem.

Your programs are getting bigger.

At the beginning, one file was enough.

Something like:

main.py

Nice.

Simple.

Friendly.

But then you added functions.

Then lists.

Then dictionaries.

Then files.

Then error handling.

And suddenly main.py becomes huge.

A giant file.

A code lasagna.

A digital wardrobe where everything is inside, but nobody knows where the socks are.

This is where modules and imports help.

Modules let you split your code into multiple files.

Imports let you reuse code from one file inside another file.

Instead of one giant file, you can organize your project like this:

main.py
helpers.py
tasks.py
calculator.py

Much cleaner.

Much more professional.

Much less “where is that function hiding?”

What You Will Learn

In this lesson, you will learn:

By the end of this lesson, your code will be more organized.

You will be able to split bigger programs into smaller files.

You will reuse functions instead of copying them.

You will make your projects easier to read.

And your future self will suffer less.

A noble goal.

What Is a Module?

A module is just a Python file.

Yes.

Really.

If you have a file called:

helpers.py

That file is a module.

If you have a file called:

calculator.py

That file is a module.

A module can contain:

For now, we will mostly use modules to store functions.

Example:

helpers.py
def greet_user(name):
    print(f"Hello, {name}!")

Then another file can import and use this function.

That is the main idea.

Write code in one file.

Use it in another file.

Very useful.

Very clean.

Very Python.

Why Use Modules?

Modules help you organize code.

Instead of one huge file, you can split your program into smaller parts.

Example:

main.py        starts the program
helpers.py     general helper functions
tasks.py       task-related functions
contacts.py    contact-related functions

This makes your project easier to understand.

A giant file is hard to read.

A project with clear files is easier to navigate.

Think of modules like drawers.

One drawer for socks.

One drawer for shirts.

One drawer for mysterious cables you refuse to throw away.

Code needs drawers too.

Without organization, everything becomes chaos.

And chaos already has enough work.

Your First Module

Create a folder:

python_modules_lesson

Inside it, create two files:

main.py
helpers.py

Your folder should look like this:

python_modules_lesson/
├── helpers.py
└── main.py

In helpers.py, write:

def greet_user(name):
    print(f"Hello, {name}!")

In main.py, write:

import helpers

helpers.greet_user("Anna")

Run:

python main.py

or:

python3 main.py

Output:

Hello, Anna!

Congratulations.

You created your own module.

Very fancy.

Very organized.

Very “look, my code has separate rooms now”.

How import Works

This line imports the module:

import helpers

It tells Python:

Load the helpers.py file so I can use what is inside it.

Then you can call the function like this:

helpers.greet_user("Anna")

Why helpers.?

Because the function lives inside the helpers module.

The full name is:

helpers.greet_user

This is useful because it shows where the function comes from.

Clear code.

No guessing.

No detective work.

No “which file created this function?”

Python tells you.

Importing with from

There is another way.

Instead of importing the whole module, you can import one function directly.

In main.py, write:

from helpers import greet_user

greet_user("Anna")

Output:

Hello, Anna!

Now you do not need:

helpers.greet_user()

You can call:

greet_user()

because you imported the function directly.

Both styles work.

Style 1:

import helpers

helpers.greet_user("Anna")

Style 2:

from helpers import greet_user

greet_user("Anna")

Both are useful.

The first style is clearer about where the function comes from.

The second style is shorter.

Programming is full of tradeoffs.

Like life.

But with more brackets.

Importing Multiple Functions

In helpers.py, write:

def greet_user(name):
    print(f"Hello, {name}!")

def say_goodbye(name):
    print(f"Goodbye, {name}!")

In main.py, write:

from helpers import greet_user, say_goodbye

greet_user("Anna")
say_goodbye("Anna")

Output:

Hello, Anna!
Goodbye, Anna!

You can import multiple functions from the same module.

Separate them with commas:

from helpers import greet_user, say_goodbye

This is useful when a module has several helper functions.

But do not import too many things blindly.

If the import line becomes very long, maybe your module is doing too much.

Or maybe your project is growing.

Both are possible.

Both deserve attention.

Importing Everything

You may see this:

from helpers import *

This imports everything from helpers.py.

It works.

But beginners should be careful with it.

Why?

Because it becomes unclear where things come from.

Example:

from helpers import *

greet_user("Anna")
say_goodbye("Anna")

This works.

But when your project grows, it can become confusing.

Where did greet_user() come from?

Where did say_goodbye() come from?

Which module?

Which file?

Which drawer?

Mystery.

Usually, it is better to be explicit:

from helpers import greet_user, say_goodbye

Clear is better than magical.

Magic is nice in movies.

Less nice in debugging.

Module Names

Module names should be simple.

Good:

helpers.py
tasks.py
contacts.py
calculator.py
file_utils.py

Bad:

my helper functions.py
task-manager.py
123tasks.py
import.py

Use lowercase letters and underscores.

Good:

file_utils.py
safe_input.py
task_manager.py

Avoid spaces.

Avoid hyphens.

Avoid names that are Python keywords.

For example, do not name your file:

import.py

That is asking for trouble.

Python already uses import.

Do not confuse Python.

It is powerful, but it is not your therapist.

A Module for Calculator Functions

Create files:

main.py
calculator.py

In calculator.py, write:

def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

def multiply(a, b):
    return a * b

def divide(a, b):
    return a / b

In main.py, write:

import calculator

print(calculator.add(5, 3))
print(calculator.subtract(5, 3))
print(calculator.multiply(5, 3))
print(calculator.divide(10, 2))

Output:

8
2
15
5.0

Now your calculator logic lives in:

calculator.py

And your program starts in:

main.py

This is cleaner.

The main file does not need to contain every function.

It can use functions from other modules.

This is how projects grow.

Slowly.

Then suddenly.

Then you need folders.

But not yet.

One monster at a time.

A Module for Safe Input

In the previous lesson, we wrote safe input functions.

Let us move them into a module.

Create:

safe_input.py

Write:

def get_integer(prompt):
    while True:
        try:
            value = int(input(prompt))
            return value
        except ValueError:
            print("Please enter a valid whole number.")

def get_float(prompt):
    while True:
        try:
            value = float(input(prompt))
            return value
        except ValueError:
            print("Please enter a valid number.")

Now in main.py, write:

from safe_input import get_integer, get_float

price = get_float("Price: ")
quantity = get_integer("Quantity: ")

total = price * quantity

print(f"Total: {total:.2f}")

This is very nice.

Your safe input logic is reusable.

Any program can import it.

No need to copy and paste the same functions again and again.

This is progress.

This is organization.

This is less suffering.

Excellent.

Importing Standard Library Modules

Python already comes with many modules.

This is called the standard library.

Examples:

math
random
datetime
os
pathlib
json

You can import them too.

Example with math:

import math

print(math.sqrt(16))

Output:

4.0

Example with random:

import random

number = random.randint(1, 10)

print(number)

This prints a random number between 1 and 10.

Python gives you many tools for free.

You do not have to write everything yourself.

Very nice.

Very generous.

Very “please use the toolbox before building a hammer from scratch”.

Example: Random Choice

Create a file:

random_example.py

Write:

import random

names = ["Anna", "Marco", "Sofia", "Luca"]

chosen_name = random.choice(names)

print(f"Chosen name: {chosen_name}")

Output may be:

Chosen name: Sofia

Or:

Chosen name: Marco

Because it is random.

The random module has useful functions like:

random.randint(1, 10)
random.choice(names)

This is good for:

games
random exercises
test data
small experiments

Randomness is useful.

Also dangerous.

Especially when naming files.

Do not name production files randomly.

Future you will cry.

Example: math Module

Create:

math_example.py

Write:

import math

radius = 5

area = math.pi * radius ** 2

print(f"Area: {area:.2f}")

Output:

Area: 78.54

The math module gives us:

math.pi
math.sqrt()
math.floor()
math.ceil()

Very useful for calculations.

You do not need to define pi yourself.

Python already has it.

Do not write:

pi = 3.14

unless you really want a tiny approximation wearing cheap shoes.

Use:

math.pi

Much better.

Code That Runs When Imported

Important.

Very important.

Create helpers.py:

def greet_user(name):
    print(f"Hello, {name}!")

print("This is helpers.py")

Create main.py:

import helpers

helpers.greet_user("Anna")

Run main.py.

Output:

This is helpers.py
Hello, Anna!

Why did Python print:

This is helpers.py

Because when you import a module, Python runs the top-level code in that file.

That means code outside functions can run during import.

This can surprise beginners.

Very much.

So be careful.

A module should usually contain functions and constants.

Not random program execution.

Otherwise importing the module becomes spicy.

And not in a good way.

The name == "main" Pattern

Sometimes you want code to run only when the file is executed directly.

Not when it is imported.

Python has a common pattern for this:

if __name__ == "__main__":
    code_here

Example:

def greet_user(name):
    print(f"Hello, {name}!")

if __name__ == "__main__":
    greet_user("Test User")

Now:

This is very useful.

It lets you test a module without causing surprises when importing it.

At first, this line looks strange:

if __name__ == "__main__":

Do not panic.

It basically means:

Run this code only if this file is the main file being executed.

Weird-looking.

Very useful.

Very Python.

Testing a Module Safely

Create helpers.py:

def greet_user(name):
    return f"Hello, {name}!"

if __name__ == "__main__":
    print(greet_user("Anna"))

If you run:

python helpers.py

Output:

Hello, Anna!

Now create main.py:

from helpers import greet_user

message = greet_user("Marco")

print(message)

Run:

python main.py

Output:

Hello, Marco!

Notice that the test code inside:

if __name__ == "__main__":

did not run when imported.

Perfect.

Clean.

Professional.

Less surprise.

And in programming, fewer surprises are usually good.

Unless it is your birthday.

But even then, not in production.

Splitting a Program into Files

Let us organize a small project.

Create this structure:

task_project/
├── main.py
├── tasks.py
└── safe_input.py

In safe_input.py, write:

def get_integer(prompt):
    while True:
        try:
            value = int(input(prompt))
            return value
        except ValueError:
            print("Please enter a valid whole number.")

In tasks.py, write:

FILE_NAME = "tasks.txt"

def load_tasks():
    tasks = []

    try:
        with open(FILE_NAME, "r") as file:
            for line in file:
                tasks.append(line.strip())
    except FileNotFoundError:
        pass

    return tasks

def save_tasks(tasks):
    with open(FILE_NAME, "w") as file:
        for task in tasks:
            file.write(task + "\n")

def add_task(tasks):
    task = input("Task: ")
    tasks.append(task)
    save_tasks(tasks)
    print("Task saved.")

def show_tasks(tasks):
    if len(tasks) == 0:
        print("No tasks yet.")
    else:
        print("Tasks:")

        for index, task in enumerate(tasks, start=1):
            print(f"{index}. {task}")

def remove_task(tasks, task_number):
    index = task_number - 1

    if index < 0 or index >= len(tasks):
        print("Invalid task number.")
        return

    removed_task = tasks.pop(index)
    save_tasks(tasks)

    print(f"Removed: {removed_task}")

In main.py, write:

from safe_input import get_integer
from tasks import load_tasks, add_task, show_tasks, remove_task

def show_menu():
    print("----- Task Manager -----")
    print("1. Add task")
    print("2. Show tasks")
    print("3. Remove task")
    print("q. Quit")

tasks = load_tasks()

while True:
    show_menu()

    choice = input("Choose an option: ").lower()

    if choice == "1":
        add_task(tasks)
    elif choice == "2":
        show_tasks(tasks)
    elif choice == "3":
        if len(tasks) == 0:
            print("No tasks to remove.")
        else:
            show_tasks(tasks)
            task_number = get_integer("Task number to remove: ")
            remove_task(tasks, task_number)
    elif choice == "q":
        print("Goodbye.")
        break
    else:
        print("Unknown option.")

This project is much better organized.

main.py controls the program flow.

tasks.py contains task logic.

safe_input.py contains safe input logic.

This is clean.

This is readable.

This is real project structure.

Small project.

But real structure.

Very good.

Why This Structure Is Better

Look at the files:

main.py
tasks.py
safe_input.py

Each file has a job.

main.py:

Runs the program.
Shows the menu.
Handles user choices.

tasks.py:

Loads tasks.
Saves tasks.
Adds tasks.
Shows tasks.
Removes tasks.

safe_input.py:

Gets safe integer input.

This is called separation of responsibilities.

Fancy phrase.

Simple idea:

Do not put everything everywhere.

Good organization makes code easier to maintain.

When you need task logic, go to tasks.py.

When you need input logic, go to safe_input.py.

When you need program flow, go to main.py.

No treasure hunt.

No code archaeology.

Much better.

Common Mistake: Module Not Found

You may see:

ModuleNotFoundError

Example:

import helpers

But Python says:

ModuleNotFoundError: No module named 'helpers'

Possible reasons:

helpers.py does not exist
helpers.py is in another folder
you misspelled the file name
you are running Python from the wrong place

For beginners, keep files in the same folder.

Example:

project/
├── main.py
└── helpers.py

Then run from that folder:

python main.py

Simple.

Less drama.

Python imports are powerful.

But paths can become spicy.

Start simple.

Common Mistake: Wrong File Name

If your file is called:

helper.py

But you write:

import helpers

Python will not find it.

Names must match.

File:

helpers.py

Import:

import helpers

No .py in the import.

Correct:

import helpers

Wrong:

import helpers.py

Python imports module names, not file names with extensions.

Tiny detail.

Big confusion.

Very normal.

Common Mistake: Circular Imports

Circular imports happen when two files import each other.

Example:

a.py imports b.py
b.py imports a.py

This can cause confusing errors.

Beginners should avoid this.

A simple rule:

main.py can import other modules.
Helper modules should usually not import main.py.

Good:

main.py imports tasks.py
main.py imports safe_input.py

Risky:

tasks.py imports main.py
main.py imports tasks.py

That creates a loop.

A snake eating its own tail.

Interesting symbol.

Annoying bug.

Avoid.

Common Mistake: Putting Program Logic in Imported Modules

Bad helpers.py:

def greet_user(name):
    print(f"Hello, {name}!")

name = input("Name: ")
greet_user(name)

If you import this module, it asks for input immediately.

Surprise.

Not good.

Better:

def greet_user(name):
    print(f"Hello, {name}!")

if __name__ == "__main__":
    name = input("Name: ")
    greet_user(name)

Now the input only happens when you run helpers.py directly.

Not when you import it.

This is much safer.

Remember:

Modules should usually define reusable things.
main.py should usually run the program.

Very good rule.

Common Mistake: Importing Too Much

This can become messy:

from helpers import *
from tasks import *
from calculator import *

Now your namespace is full of imported names.

You may not know where anything came from.

Better:

import helpers
import tasks

or:

from helpers import greet_user
from tasks import load_tasks, save_tasks

Be clear.

Explicit imports make code easier to understand.

Future you likes explicit imports.

Future you is tired and has no patience for mystery.

Respect future you.

Practice

Create this project:

calculator_project/
├── main.py
└── calculator.py

In calculator.py, create functions:

def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

def multiply(a, b):
    return a * b

def divide(a, b):
    return a / b

In main.py, import the functions and ask the user for two numbers.

Example solution:

from calculator import add, subtract, multiply, divide

first_number = float(input("First number: "))
second_number = float(input("Second number: "))

print(f"Add: {add(first_number, second_number)}")
print(f"Subtract: {subtract(first_number, second_number)}")
print(f"Multiply: {multiply(first_number, second_number)}")

if second_number == 0:
    print("Cannot divide by zero.")
else:
    print(f"Divide: {divide(first_number, second_number)}")

This practice teaches:

Very useful.

Very clean.

Very beginner-friendly.

Mini Challenge

Create this project:

notes_project/
├── main.py
└── notes.py

In notes.py, create functions:

add_note()
show_notes()

Use a file called:

notes.txt

Example notes.py:

FILE_NAME = "notes.txt"

def add_note():
    note = input("Note: ")

    with open(FILE_NAME, "a") as file:
        file.write(note + "\n")

    print("Note saved.")

def show_notes():
    try:
        with open(FILE_NAME, "r") as file:
            print("Notes:")

            has_notes = False

            for line in file:
                print(f"- {line.strip()}")
                has_notes = True

            if not has_notes:
                print("No notes yet.")
    except FileNotFoundError:
        print("No notes yet.")

Example main.py:

from notes import add_note, show_notes

def show_menu():
    print("----- Notes App -----")
    print("1. Add note")
    print("2. Show notes")
    print("q. Quit")

while True:
    show_menu()

    choice = input("Choose an option: ").lower()

    if choice == "1":
        add_note()
    elif choice == "2":
        show_notes()
    elif choice == "q":
        print("Goodbye.")
        break
    else:
        print("Unknown option.")

This is a very good beginner project.

It uses:

Small app.

Good structure.

Very useful.

Very real.

Extra Challenge: Split Contact Book

Create:

contact_project/
├── main.py
├── contacts.py
└── safe_input.py

contacts.py should contain:

load_contacts()
save_contacts()
add_contact()
show_contacts()

main.py should contain:

menu
main loop
user choices

This is a bigger challenge.

You already know the pieces.

Now the goal is organization.

Do not write everything in one file.

Make each file responsible for one part.

This is how bigger projects stay understandable.

Or at least less terrifying.

Less terrifying is good.

Beginner Checklist

When imports do not work, check:

Are the files in the same folder?
Did I spell the module name correctly?
Did I write the import without .py?
Am I running Python from the correct folder?
Did I create circular imports?
Did I put too much running code in the imported module?
Should this code be inside if __name__ == "__main__"?
Am I importing too much with *?
Does each file have a clear job?

Import problems are common.

Very common.

Do not panic.

Usually the problem is:

wrong file name
wrong folder
wrong import style
code running during import

Fix calmly.

Computers are literal.

They do exactly what you say.

Not what you meant.

Annoying.

But fair.

Summary

Today you learned:

This is a huge step.

Your projects can now grow without becoming one giant file.

You can reuse code.

You can organize functions.

You can separate program flow from helper logic.

This is how real projects begin.

Not with one magical file that does everything.

But with small files that work together.

Clean structure.

Reusable code.

Less chaos.

Very Python.

Very professional.

Next Lesson

In the next lesson, we will learn JSON.

JSON is a very common data format.

It looks a lot like Python dictionaries and lists.

You will learn how to save structured data like this:

contact = {
    "name": "Anna",
    "email": "anna@example.com",
    "phone": "123456"
}

into a file.

And then load it back later.

This is very important for real applications.

Text files are nice.

But JSON is better for structured data.

It is used in APIs, configuration files, web apps, and many real projects.

Very useful.

Very practical.

Very next level.