Beginner

Modules and Packages

Organize and reuse code with Python's module system

Imagine you're writing a Python program that's getting bigger every day. Your single main.py file now has 500 lines of code—functions for data processing, calculations, file handling, and user interface logic all jumbled together. Finding anything takes forever, and debugging is a nightmare.

This is where modules and packages save the day. They let you split your code into organized, reusable pieces—just like organizing a messy desk into labeled drawers. Instead of one massive file, you can have specialized files working together beautifully.

What Are Modules and Packages?

What is a Module?

A module is simply a Python file (with a .py extension) that contains code you can reuse. Think of it as a toolbox—you write useful functions once in a module, then import and use them anywhere.

Example: If you create a file called math_helpers.py with some calculation functions, that file is a module. You can then import it into other Python files.

What is a Package?

A package is a folder that contains multiple modules. It's like a filing cabinet that organizes related modules together. In modern Python (3.3+), any folder with Python files can be a package, though you might see an __init__.py file in older projects (it was required before but is now optional).

Example: A folder called data_tools/ containing modules like reader.py, cleaner.py, and analyzer.py is a package.

How Does It Work?

When you use import, Python looks for the module in several places:

  1. The current directory (where your script is running)
  2. Directories listed in the PYTHONPATH environment variable
  3. Standard library directories (where built-in modules live)

Key Difference: Unlike copying and pasting code, importing creates a reference to the original module. Changes to the module affect all programs that import it—making maintenance much easier.

The Perfect Analogy

Think of your codebase as a library:

Just as you wouldn't rewrite an entire encyclopedia by hand every time you need information, you shouldn't copy code—you import it!

Visualizing Project Structure

Here's what a well-organized Python project looks like:

project/
├── main.py
├── math_utils.py
├── config.py
└── tools/
├── __init__.py (optional)
├── text_utils.py
└── data_utils.py

How it flows:

💡 Tip: The __init__.py file can be empty, but it's useful for package initialization code or controlling what gets imported with from package import *

Code Examples: From Basic to Advanced

Example 1: Creating and Using a Simple Module

Let's create a module for math operations.

Step 1: Create math_utils.py

# math_utils.py
def add(a, b):
    """Add two numbers together."""
    return a + b

def subtract(a, b):
    """Subtract b from a."""
    return a - b

def mean(numbers):
    """Calculate the average of a list of numbers."""
    return sum(numbers) / len(numbers)

Step 2: Create app.py and import the module

# app.py
import math_utils

# Use functions from the module
result1 = math_utils.add(10, 5)
print(f"10 + 5 = {result1}")  # Output: 10 + 5 = 15

result2 = math_utils.subtract(10, 5)
print(f"10 - 5 = {result2}")  # Output: 10 - 5 = 5

grades = [85, 90, 78, 92, 88]
average = math_utils.mean(grades)
print(f"Average grade: {average}")  # Output: Average grade: 86.6

💡 Why this works: Python finds math_utils.py in the same directory as app.py and loads all its functions.

Example 2: Using Import Aliases

Long module names can be tedious to type. Use aliases for convenience:

# app.py with alias
import math_utils as mu  # Shorter alias

print(mu.add(20, 15))        # Output: 35
print(mu.mean([1, 2, 3, 4])) # Output: 2.5

Example 3: Importing Specific Functions

If you only need certain functions, import them directly:

# app.py - selective import
from math_utils import add, mean

# Now use functions directly without module prefix
print(add(7, 3))           # Output: 10
print(mean([10, 20, 30]))  # Output: 20.0

# Note: subtract() is NOT available because we didn't import it
# print(subtract(10, 5))   # ❌ This would cause an error

⚠️ Common Pitfall: Using from module import * imports everything, which can cause naming conflicts. Avoid it except for very specific cases.

Example 4: Creating and Using a Package

Let's build a package with multiple modules.

Project structure:

my_app/
├── main.py
└── tools/
├── __init__.py
├── text_utils.py
└── math_utils.py

tools/text_utils.py

# tools/text_utils.py
def slugify(text):
    """Convert text to URL-friendly slug."""
    # Remove special characters and replace spaces with hyphens
    return text.lower().replace(" ", "-").replace("'", "")

def count_words(text):
    """Count words in a string."""
    return len(text.split())

tools/math_utils.py

# tools/math_utils.py
def square(n):
    """Return the square of a number."""
    return n * n

def is_even(n):
    """Check if a number is even."""
    return n % 2 == 0

main.py

# main.py
from tools import text_utils, math_utils

# Use text utilities
title = "Python Programming Guide"
slug = text_utils.slugify(title)
print(f"Slug: {slug}")  # Output: Slug: python-programming-guide

word_count = text_utils.count_words(title)
print(f"Word count: {word_count}")  # Output: Word count: 3

# Use math utilities
print(f"5 squared: {math_utils.square(5)}")      # Output: 5 squared: 25
print(f"Is 8 even? {math_utils.is_even(8)}")     # Output: Is 8 even? True

Example 5: Direct Function Import from Package

# Alternative import style
from tools.text_utils import slugify
from tools.math_utils import square, is_even

# Use functions directly
print(slugify("Hello World"))  # Output: hello-world
print(square(4))               # Output: 16
print(is_even(7))              # Output: False

Hands-On Exercise: Build Your Own Package

Let's create a practical package step-by-step. You'll build a text_tools package with useful string utilities.

Step 1: Setup Your Project Structure

Create the following folder and files:

text_project/
├── app.py
└── text_tools/
├── __init__.py (create an empty file)
└── formatters.py

How to create this:

  1. Create a folder called text_project
  2. Inside it, create a folder called text_tools
  3. Create an empty file text_tools/__init__.py
  4. Create text_tools/formatters.py (we'll add code next)
  5. Create app.py in the root text_project folder

Step 2: Write the Module Code

Open text_tools/formatters.py and add these functions:

# text_tools/formatters.py

def title_case(text):
    """Convert text to Title Case."""
    return text.title()

def reverse_words(text):
    """Reverse the order of words in a sentence."""
    words = text.split()
    return " ".join(reversed(words))

def remove_vowels(text):
    """Remove all vowels from text."""
    vowels = "aeiouAEIOU"
    return "".join(char for char in text if char not in vowels)

Step 3: Import and Use Your Package

Now open app.py and add this code:

# app.py
from text_tools.formatters import title_case, reverse_words, remove_vowels

# Test the functions
sentence = "python is awesome"

print("Original:", sentence)
print("Title case:", title_case(sentence))
print("Reversed:", reverse_words(sentence))
print("No vowels:", remove_vowels(sentence))

# Expected output:
# Original: python is awesome
# Title case: Python Is Awesome
# Reversed: awesome is python
# No vowels: pythn s wsm

Step 4: Run Your Program

Open your terminal, navigate to the text_project folder, and run:

python app.py

Step 5: Verify It Works

You should see output like this:

Original: python is awesome
Title case: Python Is Awesome
Reversed: awesome is python
No vowels: pythn s wsm

💡 Success Indicator: If you see the output above, congratulations! You've successfully created and used a custom Python package.

Troubleshooting Tips

Common Mistakes and How to Avoid Them

1. Wrong Import Path / Directory Issues

Problem: Python can't find your module even though the file exists.

# ❌ Wrong - trying to import from wrong location
import ../other_folder/utils  # Syntax error!

Why it happens: Python looks for modules relative to where you run the script, not where the file is saved.

Solution: Always run your script from the project root folder.

# ✅ Correct
cd my_project      # Go to project root first
python app.py      # Then run your script

Prevention tip: Organize your project so all imports work from a single root directory.

2. Naming Conflicts with Standard Library

Problem: Your module name shadows a built-in Python module.

# ❌ Bad - file named random.py
# This breaks Python's built-in random module!
import random  # Python imports YOUR file instead of the standard library

Why it happens: Python checks the current directory first, so your random.py takes priority over the built-in module.

Solution: Never name your files after standard library modules.

# ✅ Good naming
my_random_utils.py
game_random.py
custom_random.py

Prevention tip: Before creating a module, check if the name exists: import modulename in Python console. If it works, choose a different name.

3. Forgetting the .py Extension Issue

Problem: Including .py in import statements.

# ❌ Wrong
import math_utils.py  # SyntaxError!

# ✅ Correct
import math_utils

Why it happens: Python expects module names, not filenames.

4. Circular Imports

Problem: Two modules import each other, creating an infinite loop.

# module_a.py
from module_b import function_b  # ❌ Imports module_b

# module_b.py
from module_a import function_a  # ❌ Imports module_a - CIRCULAR!

Solution: Restructure your code to avoid circular dependencies. Move shared code to a third module.

# shared.py
def shared_function():
    pass

# module_a.py
from shared import shared_function

# module_b.py
from shared import shared_function

5. Modifying Modules Not Reflected

Problem: Changes to a module don't appear when you run the script again.

Why it happens: In interactive Python sessions (like Jupyter), modules are cached. Restarting the kernel fixes this.

Solution: Use importlib.reload() for interactive development.

import importlib
import my_module

# Make changes to my_module.py, then reload
importlib.reload(my_module)

6. Relative Import Confusion

Problem: Using relative imports (from . import module) in the wrong context.

Solution: Relative imports only work inside packages. For simple scripts, use absolute imports.

Mini-Project: Build a Modular Task Manager

Let's build a command-line task manager split into organized modules.

Project Goal

Create a simple task manager that can add, view, and save tasks to a file—all using a clean modular structure.

Requirements

Project Structure

task_manager/
├── main.py
├── modules/
├── __init__.py
├── file_handler.py
└── task_manager.py
└── tasks.txt (created automatically)

Implementation Guide

Step 1: Create modules/file_handler.py

# modules/file_handler.py
def save_tasks(tasks, filename="tasks.txt"):
    """Save tasks to a file."""
    with open(filename, "w") as file:
        for task in tasks:
            file.write(task + "\n")
    print(f"✅ Saved {len(tasks)} tasks to {filename}")

def load_tasks(filename="tasks.txt"):
    """Load tasks from a file."""
    try:
        with open(filename, "r") as file:
            tasks = [line.strip() for line in file if line.strip()]
        print(f"✅ Loaded {len(tasks)} tasks from {filename}")
        return tasks
    except FileNotFoundError:
        print(f"ℹ️  No saved tasks found. Starting fresh.")
        return []

Step 2: Create modules/task_manager.py

# modules/task_manager.py
def add_task(tasks, task):
    """Add a new task to the list."""
    tasks.append(task)
    print(f"✅ Added: '{task}'")

def display_tasks(tasks):
    """Display all tasks."""
    if not tasks:
        print("📭 No tasks yet. Add one to get started!")
        return
    
    print("\n📝 Your Tasks:")
    print("-" * 40)
    for i, task in enumerate(tasks, 1):
        print(f"{i}. {task}")
    print("-" * 40)

Step 3: Create main.py

# main.py
from modules import file_handler, task_manager

def main():
    """Main program loop."""
    print("🎯 Task Manager")
    print("=" * 40)
    
    # Load existing tasks
    tasks = file_handler.load_tasks()
    
    while True:
        print("\nOptions:")
        print("1. Add task")
        print("2. View tasks")
        print("3. Save & Exit")
        
        choice = input("\nChoose (1-3): ").strip()
        
        if choice == "1":
            task = input("Enter task: ").strip()
            if task:
                task_manager.add_task(tasks, task)
            else:
                print("❌ Task cannot be empty!")
        
        elif choice == "2":
            task_manager.display_tasks(tasks)
        
        elif choice == "3":
            file_handler.save_tasks(tasks)
            print("👋 Goodbye!")
            break
        
        else:
            print("❌ Invalid choice. Try again.")

if __name__ == "__main__":
    main()

Step 4: Run Your Task Manager

cd task_manager
python main.py

Expected Output

🎯 Task Manager
========================================
ℹ️  No saved tasks found. Starting fresh.

Options:
1. Add task
2. View tasks
3. Save & Exit

Choose (1-3): 1
Enter task: Learn Python modules
✅ Added: 'Learn Python modules'

Options:
1. Add task
2. View tasks
3. Save & Exit

Choose (1-3): 2

📝 Your Tasks:
----------------------------------------
1. Learn Python modules
----------------------------------------

Options:
1. Add task
2. View tasks
3. Save & Exit

Choose (1-3): 3
✅ Saved 1 tasks to tasks.txt
👋 Goodbye!

Bonus Challenges

  1. Add task deletion: Create a function to remove tasks by number
  2. Priority levels: Add "urgent" or "normal" tags to tasks
  3. Search feature: Find tasks by keyword

💡 What You Just Did: You created a real-world application using modular design—file operations are isolated, task logic is separate, and the main program coordinates everything. This is how professional developers structure projects!

Summary

Key Takeaways

What Makes Modules & Packages Special

Your Next Steps

Here's your action plan to master modules and packages:

🚀 You're Ready for the Next Level! Modules and packages are the foundation of professional Python development. Every major project—from Django to TensorFlow—uses this exact organizational structure. You're now thinking like a real developer!

Remember: Good code isn't just about what it does—it's about how it's organized. Modules and packages are your tools for building maintainable, scalable Python applications. Start small, stay organized, and watch your coding superpowers grow! 💪

🎯 Test Your Knowledge: Modules & Packages

Check your understanding with this quick quiz

1. What is a Python module?

A folder containing multiple Python files
A single .py file that contains reusable code
A built-in Python function
A type of Python variable

2. Which import statement is correct?

import math_utils.py
import math_utils
import "math_utils"
import math_utils.python

3. What's the main purpose of organizing code into modules and packages?

To make programs run faster
To reduce file size
To improve code organization and reusability
To make Python code look more complex

4. If you name your file "random.py", what problem might occur?

Python will refuse to run it
It will shadow Python's built-in random module
It will automatically delete itself
Nothing, it's a perfectly fine name

5. What does "from math_utils import add, subtract" do?

Imports the entire math_utils module
Imports only the add and subtract functions
Creates new functions called add and subtract
Deletes all other functions from math_utils
← Previous: Functions Next: String Methods →