Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

Organize Code Into Modules

Learn how to organize Python code into modules and packages for better structure and reusability. Hands-on lesson with practical examples, troubleshooting, and next steps.

Focus: organize code into modules and packages

Sponsored

Ever opened a Python script that’s thousands of lines long? It’s a nightmare to debug, reuse, or even read. Without modular code, you’ll fight with duplicate logic, fragile imports, and a wall of text that breaks every time you touch a single function. That’s why mastering how to organize code into modules and packages is a superpower every Python developer needs — it turns chaotic scripts into clean, maintainable, and shareable libraries.

The problem this lesson solves

As your Python projects grow past a single file, you hit three hard walls:

  • Maintenance nightmare — find-and-replace across 2 000 lines when a constant changes.
  • Reusability zero — copy-paste the same 50-line helper into every new script because there’s no shared import.
  • Team collaboration chaos — two people edit the same file and step on each other’s functions.

Without modules and packages, you can’t scale. You end up with spaghetti code that no one (including future you) wants to touch. The fix is simple: split your code into logical files (modules) and group them into folders (packages).

Core concept / mental model

Think of a module as a single notebook — one .py file with related functions, classes, or constants. A package is a binder that holds several notebooks, organized by topic.

A module is a file; a package is a folder with an __init__.py module inside.

Here’s the mental model:

  • math_ops.py — a module with add, subtract, multiply.
  • file_io/ — a package with reader.py, writer.py, validator.py.

When you import math_ops, Python runs that file and makes its namespace available. When you import file_io.reader, Python looks for file_io/ (a package), then reader.py (a module).

Key definitions

Term What it is Example
Module A single .py file greetings.py
Package A directory with __init__.py utils/
Subpackage A package inside another package utils/io/
Import Statement that loads a module import utils.reader

The __init__.py file tells Python: “this folder is a package.” It can be empty, or it can run setup code (like pulling in submodules).

How it works step by step

Step 1 — Create a module

Write a file helpers.py:

# helpers.py
def greet(name):
    return f"Hello, {name}!"

PI = 3.14159

Now in another file main.py:

# main.py
import helpers
print(helpers.greet("Alice"))   # Hello, Alice!
print(helpers.PI)                # 3.14159

Python searches sys.path (current folder + installed packages) for helpers.py and runs it once.

Step 2 — Turn a folder into a package

Create a folder formulas/ with an __init__.py (can be empty). Add modules inside:

project/
├── main.py
└── formulas/
    ├── __init__.py
    ├── geometry.py
    └── statistics.py
# formulas/geometry.py
def area_of_circle(radius):
    return 3.14159 * radius * radius
# formulas/__init__.py
# This file can be empty; it just marks the folder as a package.

In main.py:

# main.py
from formulas.geometry import area_of_circle
print(area_of_circle(5))   # 78.53975

Step 3 — Absolute vs. relative imports

  • Absolute import — use the full path from project root: from formulas.geometry import area_of_circle
  • Relative import — use . for current package or .. for parent: from .geometry import area_of_circle (inside formulas/__init__.py)

Relative imports only work inside packages, never in the top-level script.

Hands-on walkthrough

Let’s build a small project with two modules inside one package.

Project structure

calculator_app/
├── main.py
└── calc/
    ├── __init__.py
    ├── arithmetic.py
    └── stats.py

Step 1 — Write the package calc

# calc/__init__.py
from .arithmetic import add, subtract
from .stats import mean

This makes from calc import add work directly.

# calc/arithmetic.py
def add(a, b):
    return a + b

def subtract(a, b):
    return a - b
# calc/stats.py
def mean(numbers):
    return sum(numbers) / len(numbers)

Step 2 — Write main.py

# main.py
from calc import add, subtract, mean

print(add(10, 5))            # 15
print(subtract(10, 5))       # 5
print(mean([1, 2, 3, 4, 5])) # 3.0

Run from within calculator_app/:

$ python main.py
15
5
3.0

Step 3 — Add a subpackage

calc/
├── __init__.py
├── arithmetic.py
├── stats.py
└── advanced/
    ├── __init__.py
    ├── matrix.py

Absolute import in calc/__init__.py:

from .advanced.matrix import multiply_matrices

Now main.py can from calc import multiply_matrices.

Compare options / when to choose what

Scenario Recommended approach Why
1–3 small functions No module needed; keep in main script Overhead of modules isn’t worth it
10–50 lines of related logic Single module (.py file) Simple import, easy to test
Multiple related modules Package (folder with __init__.py) Logical grouping, discoverability
Large project with sub-teams Namespace packages (no __init__.py in Python 3.3+) Avoid name collisions, extensible
Sharing code across projects Installable package with setup.py or pyproject.toml Reuse via pip, versioning

When to avoid packages

  • If the module is one-off and never reused across projects, a single .py file is fine.
  • Over-engineering — don’t create an __init__.py for a single file unless you know you’ll add more later.

Troubleshooting & edge cases

1. ModuleNotFoundError: No module named 'calc'

Cause: Python can’t find the package because it’s not in the import path.

Fix: Run your script from the parent directory of the package, or use a relative import if inside the package.

# from calculator_app/ (not from inside calc/)
python main.py

2. Circular imports

Two modules import each other (e.g., a.py imports b, b.py imports a). This raises ImportError at runtime.

Fix: Restructure — put shared code in a third module, or use lazy imports inside functions (not at top level).

# inside a function
from calc.b import some_function

3. __init__.py forgotten

If calc/ has no __init__.py, Python treats it as an implicit namespace package, but in older docs and tools it’s required. Always add an empty __init__.py until you need advanced features.

4. Import runs the module twice

Python caches modules in sys.modules. If you import the same module with different paths (e.g., import calc.arithmetic and from calc import arithmetic), it still runs only once. No double execution — Python is smart.

5. Relative imports in top-level scripts

from . import helpers   # SyntaxError: attempted relative import beyond top-level package

Fix: Use absolute imports from outside the package, or run the file as a module with python -m.

What you learned & what's next

You can now:

  • Explain why modules and packages are essential for code organization.
  • Create a module (.py file) and import it using import or from ... import.
  • Build a package by adding __init__.py to a folder.
  • Distinguish between absolute and relative imports.
  • Troubleshoot common import errors like ModuleNotFoundError and circular imports.

The next step is managing dependencies with virtual environments and pip — so you can organize not just your own code, but third-party libraries too. Ready to clean up your projects and make your Python code truly reusable.

Practice recap

Create a folder my_package/ with __init__.py and two modules: greet.py (with a hello() function) and math_ops.py (with add() and subtract()). Then write a main.py that imports both modules using from my_package import ... and runs each function. Extend by adding a subpackage my_package/advanced/ with a complex_ops.py module.

Common mistakes

  • Forgetting to add __init__.py to a package folder — this is one of the most common ModuleNotFoundError causes.
  • Using relative imports (from . import module) in the top-level script — Python refuses because it can't determine the package root.
  • Creating a circular import by having module A import module B and module B import module A; fix by lazy-importing inside functions.
  • Running python calc/main.py instead of python main.py from the parent directory, causing import paths to break.

Variations

  1. Use from package import * with __all__ in __init__.py to control what gets imported — but avoid in production due to namespace pollution.
  2. Namespace packages (no __init__.py) in Python 3.3+ allow splitting a package across multiple directories, useful for large projects.
  3. PEX (Python EXecutable) bundles modules and packages into a single file; great for deployment but less flexible for development.

Real-world use cases

  • Organize a Django web app's views, models, and tests into separate packages inside the project folder.
  • Share utility functions across multiple scripts in a data science pipeline by grouping them into utils/ package.
  • Create a reusable library with versioned subpackages (e.g., mylib.v1 and mylib.v2) for backward-compatible APIs.

Key takeaways

  • A module is a single .py file; a package is a directory with an __init__.py file.
  • Always run your main script from the parent directory of your packages to avoid import path errors.
  • Use absolute imports (from package.module import function) for clarity and portability.
  • Circular imports can be avoided by moving shared code to a separate module or using lazy imports.
  • The __init__.py can be empty, but it can also pre-import submodules for simpler from package import syntax.
  • Python caches imported modules, so you only pay the execution cost once.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.