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.
# 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"
π» 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
- Install Python 3.10+ β Download from python.org and verify with
python --versionin your terminal. - Create a project folder β e.g.,
mkdir myproject && cd myproject. - Create a virtual environment β
python -m venv .venvand activate it (see the venv section below). - Create the files β Copy each code snippet into a
.pyfile (e.g.,math_utils.py,greetings.py, etc.) inside your project folder, respecting the package structure shown (folders with__init__.py). - Run your main script β From the terminal, execute
python main.py(or whichever file contains your entry point). - Install external packages β If you need packages like
requestsorpandas, install them inside your virtual environment withpip 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."
π 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-learntotransformers, 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__.pyand 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
importandusethem 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.
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.
PYTHONPATHenvironment 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.
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.
__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.
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.
__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.
__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.
π 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
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.
π Document Every Module
Every module, class, and function should have a docstring. Use __all__ to document the public API.
π§ͺ Use Absolute Imports
In most cases, absolute imports are clearer and more maintainable than relative imports. Use relative imports only within a package.
π Always Use a Virtual Environment
Never install packages globally. Each project gets its own .venv and requirements.txt or pyproject.toml.
π¦ Name Your Package Clearly
Choose a unique, memorable name for your package. If publishing to PyPI, check availability first. Use lowercase with underscores.
π 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.
β 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://....
π¬ Python, weekly
Short lessons, real code, and ecosystem news β every week
π£οΈ Learner Voices
No comments yet β your voice starts the conversation.