Intermediate Β· Python 3.12+

Python Modules & Packages

Organise your code into reusable, maintainable, and shareable components. Learn how imports work, how to structure packages, and how to manage dependencies with pip and virtual environments.

project / main.py
# Importing a module you wrote
from mypackage.utils import clean_text
from mypackage.models import User

user = User(name="  Glenn  ")
user.name = clean_text(user.name)
print(user.name)   # "Glenn"
Pyodide loading…

πŸ’» Running Code Locally – Step-by-Step

Many examples in this lesson involve creating multiple files and importing between them. The in‑browser runner works for simple snippets, but for full module and package demonstrations, you need to run the code on your own machine.

How to run these examples on your computer

  1. Install Python 3.10+ – Download from python.org and verify with python --version in your terminal.
  2. Create a project folder – e.g., mkdir myproject && cd myproject.
  3. Create a virtual environment – python -m venv .venv and activate it (see the venv section below).
  4. Create the files – Copy each code snippet into a .py file (e.g., math_utils.py, greetings.py, etc.) inside your project folder, respecting the package structure shown (folders with __init__.py).
  5. Run your main script – From the terminal, execute python main.py (or whichever file contains your entry point).
  6. Install external packages – If you need packages like requests or pandas, install them inside your virtual environment with pip install requests.

πŸ’‘ The browser‑based β€œRun” button works for single‑file code, but imports that reference other files or package structures will likely fail because the runner does not have access to your file system. Always use your local Python environment for module and package exercises.

πŸ“¦ What Are Modules & Packages?

As your Python programs grow beyond a single file, you need a way to organise code into logical, reusable units. Modules and packages are Python's answer.

Why Every Programmer Must Learn This Curriculum

A single-file script is fine for learning β€” but the moment you build a real application, you need structure. Modules and packages give you that structure. They let you separate concerns (data layer vs UI layer vs business logic), share code across multiple scripts, and control exactly what parts of your code are public and what's internal.

Without this topic, you can write scripts. With it, you can write applications β€” codebases that are testable, maintainable, and ready to grow. This is the skill that separates "Python hobbyist" from "Python professional."

30+ runnable examples Package structures Intermediate 1–1.5 hours

🌍 Significance in the Programming World

Almost every production Python project you will ever touch is built around modules and packages. The open‑source ecosystem β€” from Django and Flask to NumPy and TensorFlow β€” is a massive constellation of packages. Understanding how these packages are structured, imported, and versioned is essential for reading their source code, contributing to them, or even just using them without constant confusion.

In the era of AI, where models are packaged and deployed as libraries, the skill of modularisation is more important than ever. You don't just glue AI components together β€” you compose them, and composition demands clean, well-defined modules. Every data scientist who writes a pip install command is relying on the packaging infrastructure you will learn in this tutorial. Every ML engineer who builds a reusable pipeline must structure it as a package. The days of "notebook-only" AI are fading; the future is production-grade, modular AI systems, and that future starts with modules and packages.

  • Software Engineering: Clean separation of concerns, testability, and team collaboration.
  • Open Source: Every popular Python library is a package you can inspect, import, and extend.
  • Data Science & AI: From scikit-learn to transformers, models are packaged and shared as modules.
  • DevOps & Automation: Infrastructure code is written in reusable modules that deploy services reliably.
  • Career Growth: Senior developers are distinguished by their ability to design systems, not just write functions β€” and system design begins with modularity.

πŸ€– Its Role in the Era of AI

AI has shifted from "research experiments in notebooks" to "production systems in packages." The most successful AI engineers are not just model‑builders; they are software engineers who understand AI. And the foundation of that understanding is modular, reusable, importable code.

Consider these facts:

  • ✨ Every major AI library β€” PyTorch, TensorFlow, JAX, Hugging Face Transformers β€” is structured as a package. Understanding __init__.py and relative imports is the first step to reading their source code.
  • ✨ AI pipelines are composed of modules: data loaders, preprocessors, model definitions, evaluation functions. Each is a Python module. When you structure your pipeline as a package, you make it repeatable, testable, and shareable.
  • ✨ Deploying AI models to production (via AWS SageMaker, Azure ML, or even a simple Flask API) means packaging them so that a Python process can import and use them reliably. That is exactly what this tutorial teaches.
  • ✨ The AI community moves fast. New models and tools are released daily via pip install. If you understand packages, you understand how to consume those innovations and how to contribute your own.

In short: If you want to build AI, you learn algorithms. If you want to deploy AI, you learn modules and packages. This tutorial is your bridge from the notebook to production.

🧩 Module = One .py File

A module is simply a file containing Python definitions and statements. The file name is the module name with the .py suffix. Modules allow you to group related functions, classes, and constants together.

# math_utils.py β€” a module
def add(a, b): return a + b
def subtract(a, b): return a - b
PI = 3.14159
πŸ“ Save this as math_utils.py in your project folder.

Any Python file you create can be imported as a module. The standard library itself is a collection of modules (os, sys, json, etc.).

πŸ“ Package = Folder with __init__.py

A package is a directory that contains a special __init__.py file (which can be empty). Packages allow you to organise related modules into a hierarchy.

# myproject/
# β”œβ”€β”€ __init__.py        # marks this as a package
# β”œβ”€β”€ main.py
# β”œβ”€β”€ utils/
# β”‚   β”œβ”€β”€ __init__.py
# β”‚   β”œβ”€β”€ string_helpers.py
# β”‚   └── math_helpers.py
# └── models/
#     β”œβ”€β”€ __init__.py
#     └── user.py
πŸ“ Create these folders and files in your project directory.

Packages let you structure large codebases cleanly. They also enable relative imports and control the public API via __all__.

Why This Matters

Without modules and packages, every Python project would be a single, unmaintainable file. With them, you can:

  • βœ“ Reuse code across multiple files and projects
  • βœ“ Keep your code organised and easy to navigate
  • βœ“ Share your work by publishing packages to PyPI
  • βœ“ Isolate dependencies with virtual environments

↗️ Import Statements β€” The Gateway to Modules

Every Python file that wants to use another module must import it. The import system is flexible β€” import whole modules, specific names, or even rename them.

πŸ“₯ Basic Import Forms

# 1. Import the whole module
import math
print(math.sqrt(16))

# 2. Import with an alias
import math as m
print(m.pi)

# 3. Import specific names from a module
from math import sqrt, pi
print(sqrt(25))
print(pi)

# 4. Import everything (generally discouraged)
from math import *
print(sin(0))   # messy, pollutes namespace

# 5. Import a module with a shorter alias
import pandas as pd
import numpy as np

df = pd.DataFrame({"score": [88, 92, 79]})
print(df["score"].mean())
print(np.array([1, 2, 3]) * 2)
πŸ” These imports work directly in any Python file. No extra files needed.
πŸ’‘ The import * form is strongly discouraged in most codebases because it pollutes the global namespace and makes it unclear which names come from which module. Be explicit β€” import only what you need.

πŸ” How Python Finds Modules

When you write import something, Python searches for something in this order:

  • 1. The directory containing the script you're running
  • 2. PYTHONPATH environment variable directories
  • 3. The standard library directories
  • 4. Site-packages (where pip installs third-party packages)
import sys
print(sys.path)   # List of directories Python searches
πŸ‘€ Run this to see where Python looks for modules on your system.
✨ You can add custom paths to sys.path at runtime, but it's cleaner to structure your project properly and install your own packages in development mode.

πŸ”„ Relative Imports

Inside a package, you can import other modules from the same package using relative imports with dots.

import sys, types, textwrap

# --- simulating myproject/utils/math_helpers.py and myproject/models/user.py ---
# (on your machine these already exist as separate files β€” no simulation needed)
for name in ["myproject", "myproject.utils", "myproject.models"]:
    pkg = types.ModuleType(name)
    pkg.__path__ = []
    sys.modules[name] = pkg

math_helpers = types.ModuleType("myproject.utils.math_helpers")
math_helpers.clamp = lambda n, low, high: max(low, min(n, high))
sys.modules["myproject.utils.math_helpers"] = math_helpers

user = types.ModuleType("myproject.models.user")
user.User = type("User", (), {"__repr__": lambda self: "<User>"})
sys.modules["myproject.models.user"] = user

# --- myproject/utils/string_helpers.py ---
string_helpers_src = """
from . import math_helpers        # import from same package
from ..models import user         # import from sibling package
from .math_helpers import clamp   # import specific name

print("clamp(15, 0, 10) ->", clamp(15, 0, 10))
print("imported ok:", math_helpers.__name__, "and", user.__name__)
"""

string_helpers = types.ModuleType("myproject.utils.string_helpers")
string_helpers.__package__ = "myproject.utils"
exec(compile(textwrap.dedent(string_helpers_src), "string_helpers.py", "exec"), string_helpers.__dict__)
πŸ“ The block above simulates a package purely in memory so the relative imports actually run here. On your machine, just create the real files as shown and skip the simulation lines.
⚠️ Relative imports only work inside a package. They cannot be used in a script that is executed directly (__name__ == "__main__"). For scripts, use absolute imports.

πŸ“„ Creating Your Own Modules

Any .py file is a module. Here's how to write, import, and use your own modules effectively.

πŸ“ A Simple Module

Create a file called greetings.py with the following content:

# greetings.py
"""A simple module for greeting people."""

def hello(name: str) -> str:
    """Return a friendly greeting."""
    return f"Hello, {name}!"

def goodbye(name: str) -> str:
    return f"Goodbye, {name}!"

# This code runs only when the module is executed directly
if __name__ == "__main__":
    print(hello("World"))
πŸ“ Save as greetings.py and run it with python greetings.py to test.

Now import and use it in another file (e.g., main.py):

# main.py
import sys, types, textwrap

# --- simulating greetings.py sitting next to main.py ---
# (on your machine this block isn't needed β€” the real file already exists)
_greetings_src = '''
def hello(name):
    return f"Hello, {name}!"

def goodbye(name):
    return f"Goodbye, {name}!"
'''
greetings = types.ModuleType("greetings")
sys.modules["greetings"] = greetings
exec(compile(textwrap.dedent(_greetings_src), "greetings.py", "exec"), greetings.__dict__)

import greetings
print(greetings.hello("Alice"))
print(greetings.goodbye("Bob"))
πŸ“ The setup lines above simulate greetings.py existing as its own file so this runs live here. On your machine, just keep main.py next to greetings.py, skip the simulation, and run python main.py.
πŸ’‘ The if __name__ == "__main__" guard allows a module to be both imported and run as a script. Code inside this block executes only when you run the file directly (python greetings.py), not when it's imported.

πŸ“‚ Module Search Path

If your module is not in the same directory as your script, you have three options:

  • 1. Place it in the same directory as the script
  • 2. Add its directory to PYTHONPATH
  • 3. Install it as a package using pip install -e . (editable install)

πŸ“ Packages β€” Organising Modules into Hierarchies

Packages are how you structure large projects. A package is a directory with an __init__.py file.

πŸ“ Package Structure

Here's a typical package structure for a project called analytics:

# analytics/
# β”œβ”€β”€ __init__.py
# β”œβ”€β”€ core/
# β”‚   β”œβ”€β”€ __init__.py
# β”‚   β”œβ”€β”€ stats.py
# β”‚   └── utils.py
# β”œβ”€β”€ visualization/
# β”‚   β”œβ”€β”€ __init__.py
# β”‚   β”œβ”€β”€ charts.py
# β”‚   └── tables.py
# └── data/
#     β”œβ”€β”€ __init__.py
#     └── loaders.py
πŸ“ Create these folders and files locally. Each folder must contain an __init__.py (can be empty).

To import from this package:

import sys, types, textwrap

# --- simulating the analytics/ package living on disk ---
# (on your machine these already exist as real files β€” no simulation needed)
def install_package(name):
    pkg = types.ModuleType(name)
    pkg.__path__ = []
    sys.modules[name] = pkg
    return pkg

def install_module(fullname, source):
    package = fullname.rpartition(".")[0]
    if package and package not in sys.modules:
        install_package(package)
    mod = types.ModuleType(fullname)
    mod.__package__ = package
    sys.modules[fullname] = mod
    exec(compile(textwrap.dedent(source), fullname + ".py", "exec"), mod.__dict__)
    return mod

install_package("analytics")
install_module("analytics.core.stats", '''
def mean(values):
    return sum(values) / len(values)

def median(values):
    s = sorted(values)
    mid = len(s) // 2
    return s[mid] if len(s) % 2 else (s[mid - 1] + s[mid]) / 2
''')
install_module("analytics.visualization.charts", '''
def bar_chart(values):
    return "".join("#" * v + "\\n" for v in values)
''')
install_module("analytics.data.loaders", '''
def load_csv(text):
    return [line.split(",") for line in text.strip().splitlines()]
''')

from analytics.core.stats import mean, median
from analytics.visualization.charts import bar_chart
from analytics.data.loaders import load_csv

print("mean:", mean([1, 2, 3, 4, 5]))
print("median:", median([1, 2, 3, 4, 5]))
print(bar_chart([2, 4, 1]))
print("first row:", load_csv("a,b,c\n1,2,3")[0])
πŸ“ The setup lines above simulate the analytics/ package purely in memory so the import works live here. On your machine, just create the real files and skip the simulation.

πŸ“„ The __init__.py File

The __init__.py file marks a directory as a Python package. It can be empty, but it can also contain code that runs when the package is imported β€” for example, to expose a clean public API.

# analytics/__init__.py
"""
Analytics package β€” data analysis and visualisation tools.
"""

from .core.stats import mean, median, standard_deviation
from .visualization.charts import bar_chart, line_chart
from .data.loaders import load_csv, load_json

__all__ = ["mean", "median", "standard_deviation",
           "bar_chart", "line_chart", "load_csv", "load_json"]
πŸ“ This file goes inside the analytics/ folder. It's shown here for reference (Run is disabled since it's meant to be saved, not executed on its own) β€” the live example below puts it to work.

Now users can do:

import sys, types, textwrap

# --- simulating the whole analytics/ package, including its __init__.py ---
# (on your machine this already exists as real files β€” no simulation needed)
def install_package(name):
    pkg = types.ModuleType(name)
    pkg.__path__ = []
    sys.modules[name] = pkg
    return pkg

def install_module(fullname, source):
    package = fullname.rpartition(".")[0]
    if package and package not in sys.modules:
        install_package(package)
    mod = sys.modules.get(fullname)
    if mod is None:
        mod = types.ModuleType(fullname)
        sys.modules[fullname] = mod
    # a package's own __init__.py resolves "." against itself, not its parent
    mod.__package__ = fullname if hasattr(mod, "__path__") else package
    exec(compile(textwrap.dedent(source), fullname + ".py", "exec"), mod.__dict__)
    return mod

install_package("analytics")
install_module("analytics.core.stats", '''
def mean(values):
    return sum(values) / len(values)

def median(values):
    s = sorted(values)
    mid = len(s) // 2
    return s[mid] if len(s) % 2 else (s[mid - 1] + s[mid]) / 2

def standard_deviation(values):
    m = mean(values)
    return (sum((x - m) ** 2 for x in values) / len(values)) ** 0.5
''')
install_module("analytics.visualization.charts", '''
def bar_chart(values):
    return "".join("#" * v + "\\n" for v in values)

def line_chart(values):
    return " -> ".join(str(v) for v in values)
''')
install_module("analytics.data.loaders", '''
def load_csv(text):
    return [line.split(",") for line in text.strip().splitlines()]

def load_json(text):
    import json
    return json.loads(text)
''')
install_module("analytics", '''
from .core.stats import mean, median, standard_deviation
from .visualization.charts import bar_chart, line_chart
from .data.loaders import load_csv, load_json

__all__ = ["mean", "median", "standard_deviation",
           "bar_chart", "line_chart", "load_csv", "load_json"]
''')

import analytics
print(analytics.mean([1,2,3,4,5]))   # clean and explicit
πŸ“ The setup lines simulate the analytics/ package (including its __init__.py above) purely in memory so this runs live here. On your machine, skip the simulation β€” the real files already do this.
πŸ’‘ The __init__.py file is executed when the package is imported. This is where you can import submodules to expose them at the package level, making the API cleaner for users.

🎯 __all__ β€” Controlling What Gets Imported

The __all__ variable in a module or package defines which names are exported when someone uses from module import *.

# mymodule.py
import sys, types, textwrap

# --- simulating mymodule.py existing as its own file ---
# (on your machine this block isn't needed β€” just save the file below)
_mymodule_src = '''
def public_func():
    return "I am public!"

def _private_func():          # convention: underscore means private
    return "I am private!"

__all__ = ["public_func"]     # only public_func is exported
'''
mymodule = types.ModuleType("mymodule")
sys.modules["mymodule"] = mymodule
exec(compile(textwrap.dedent(_mymodule_src), "mymodule.py", "exec"), mymodule.__dict__)

# In another file
from mymodule import *   # only public_func is imported

print(public_func())
try:
    print(_private_func())
except NameError:
    print("_private_func was not imported β€” exactly as expected!")
πŸ“ The setup lines above simulate mymodule.py as its own file so this runs live here. On your machine, just save the file and skip the simulation.
πŸ’‘ Even without __all__, names starting with an underscore are not imported by import *. But __all__ gives you explicit control and documents the public API.

In a package, __all__ in __init__.py controls what is available when someone imports the package directly.

πŸ§ͺ Virtual Environments & pip β€” Dependency Management

Every Python project should have its own isolated environment. Virtual environments prevent dependency conflicts and keep your global Python clean.

πŸ“¦ Creating a Virtual Environment

# Create a virtual environment (Python 3.3+)
python -m venv .venv

# Activate it
# On macOS/Linux:
source .venv/bin/activate

# On Windows:
.venv\Scripts\activate

# Once activated, your prompt changes to show (.venv)
# Now install packages β€” they go into this environment only
pip install requests pandas flask
πŸ’» Run these commands in your terminal inside your project folder.
πŸ’‘ Always create a virtual environment for every Python project. It's the single most important habit for maintaining reproducible, conflict-free projects.

πŸ“‹ requirements.txt β€” Locking Dependencies

Once you have installed packages, freeze the exact versions so anyone else (or your future self) can reproduce the environment.

# Generate a requirements file
pip freeze > requirements.txt

# Install from a requirements file
pip install -r requirements.txt
πŸ“ This creates a requirements.txt file in your project root.

requirements.txt looks like this:

requests==2.31.0
pandas==2.1.4
flask==3.0.0
✨ For larger projects, consider using poetry or uv for more robust dependency management, including development vs. production dependencies.

πŸ“₯ Installing Your Own Package in Development Mode

If you're building a package, you can install it in "editable" mode so changes to the source are immediately reflected without reinstalling.

# In your project root (with setup.py or pyproject.toml)
pip install -e .

# Now you can import your package from anywhere
import mypackage
πŸ“ Requires a setup.py or pyproject.toml in your project root.

βœ… Module & Package Best Practices

Write maintainable, shareable Python code with these proven conventions.

πŸ“ Use a Clear Project Structure

Place your modules in a src/ directory or use a flat structure. Keep tests in a tests/ directory. Name your package meaningfully.

src/tests/README.md

πŸ“ Document Every Module

Every module, class, and function should have a docstring. Use __all__ to document the public API.

docstrings__all__Sphinx

πŸ§ͺ Use Absolute Imports

In most cases, absolute imports are clearer and more maintainable than relative imports. Use relative imports only within a package.

absolute importsexplicit

πŸ”’ Always Use a Virtual Environment

Never install packages globally. Each project gets its own .venv and requirements.txt or pyproject.toml.

venvrequirements.txtpip

πŸ“¦ Name Your Package Clearly

Choose a unique, memorable name for your package. If publishing to PyPI, check availability first. Use lowercase with underscores.

namingPyPI

πŸ”„ Use import at the Top

Put all imports at the top of the file, grouped by standard library, third-party, and local imports. This makes dependencies easy to see.

PEP 8import order

❓ Frequently Asked Questions

Common questions about Python modules and packages

What is the difference between a module and a package?

+

A module is a single .py file. A package is a directory containing multiple modules and a special __init__.py file. Packages are used to organise related modules into a hierarchy.

Why do we need __init__.py?

+

__init__.py marks a directory as a Python package. It can be empty, but it can also contain code that runs when the package is imported β€” for example, to expose a clean public API or configure logging. Without it, Python treats the directory as a regular folder, not a package.

What is the difference between import module and from module import name?

+

import module imports the entire module and you access its contents with module.name. from module import name imports only the specified name directly into your namespace, so you can use name without the module prefix. The latter is cleaner when you use a specific name many times, but can lead to name collisions.

How do I import a module from a parent directory?

+

Use relative imports: from .. import parent_module. This only works inside a package. For scripts, add the parent directory to sys.path or better, restructure your project as an installable package and use absolute imports.

What is if __name__ == "__main__" used for?

+

This guard prevents code from running when the module is imported. Code inside the guard runs only when the module is executed directly (python module.py). This is useful for testing, running a demo, or providing a CLI entry point while keeping the module reusable.

Should I use relative or absolute imports?

+

PEP 8 recommends absolute imports for readability: from mypackage.submodule import func. Relative imports (from . import submodule) are acceptable inside a package, especially to avoid repeating the package name. But absolute imports are generally clearer and more maintainable.

How do I share my Python package with others?

+

Write a setup.py or pyproject.toml, then build and upload to PyPI using build and twine. Or share the source via GitHub and instruct users to install with pip install git+https://....

πŸ—£οΈ Learner Voices

No comments yet β€” your voice starts the conversation.