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
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__.pymodule inside.
Here’s the mental model:
math_ops.py— a module withadd,subtract,multiply.file_io/— a package withreader.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(insideformulas/__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
.pyfile is fine. - Over-engineering — don’t create an
__init__.pyfor 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 (
.pyfile) and import it usingimportorfrom ... import. - Build a package by adding
__init__.pyto a folder. - Distinguish between absolute and relative imports.
- Troubleshoot common import errors like
ModuleNotFoundErrorand 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__.pyto a package folder — this is one of the most commonModuleNotFoundErrorcauses. - 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.pyinstead ofpython main.pyfrom the parent directory, causing import paths to break.
Variations
- Use
from package import *with__all__in__init__.pyto control what gets imported — but avoid in production due to namespace pollution. - Namespace packages (no
__init__.py) in Python 3.3+ allow splitting a package across multiple directories, useful for large projects. - 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.v1andmylib.v2) for backward-compatible APIs.
Key takeaways
- A module is a single
.pyfile; a package is a directory with an__init__.pyfile. - 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__.pycan be empty, but it can also pre-import submodules for simplerfrom package importsyntax. - Python caches imported modules, so you only pay the execution cost once.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.