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:
- what a module is;
- why modules are useful;
- how to create your own module;
- how to use
import; - how to use
from ... import; - how to import multiple functions;
- how to organize helper functions;
- how to avoid running module code accidentally;
- what
__name__ == "__main__"means; - how to split a task manager into multiple files;
- common beginner mistakes with imports.
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:
- variables;
- functions;
- lists;
- dictionaries;
- classes;
- code.
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:
- if you run
helpers.pydirectly, it printsHello, Test User!; - if you import
helpers.py, it does not run that test code.
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:
- creating a module;
- importing functions;
- using imported functions;
- separating logic from program flow.
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:
- modules;
- imports;
- functions;
- files;
- loops;
- conditions;
- error handling.
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:
- a module is a Python file;
- modules help organize code;
import module_nameimports a whole module;from module import functionimports a function directly;- you can import multiple functions;
- avoid
from module import *when possible; - module names should be simple and lowercase;
- Python has standard library modules like
mathandrandom; - code at the top level of a module runs when imported;
if __name__ == "__main__"prevents unwanted code execution during import;- larger programs can be split into files like
main.py,tasks.py, andsafe_input.py; - each file should have a clear responsibility;
- circular imports can cause confusing problems.
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.