Packages: Organize Modules for Scalable Projects

Introduction

In this chapter, you will learn Python packages and how they help you organize multiple modules in a clean project structure. A package is essential when your project grows beyond a few files. Once you understand packages, your codebase becomes easier to maintain, reuse, and scale.

Prerequisites

  • Python 3.10+ installed
  • Basic understanding of modules and import statements
  • Ability to run .py files in terminal or IDE

What Is a Package

A package is a directory that groups related Python modules.

Typical package structure:

text
my_project/
├── app.py
└── utils/
    ├── __init__.py
    ├── math_tools.py
    └── text_tools.py

In many projects, __init__.py is used to mark and manage package behavior.

Why packages matter:

  • better code organization
  • clearer import paths
  • easier teamwork and testing

1) Create and Import from a Package

Suppose you have:

  • utils/math_tools.py
  • app.py

utils/math_tools.py:

python
# Add two numbers
def add(a, b):
    return a + b

app.py:

python
# Import function from package module
from utils.math_tools import add
 
print(add(3, 5))

This is the simplest package import workflow.

2) Use __init__.py

__init__.py can:

  • mark directory as a package (traditional usage)
  • expose selected members
  • run package initialization logic

Example utils/__init__.py:

python
# Re-export functions for cleaner import paths
from .math_tools import add
from .text_tools import to_upper

Then you can import directly from package:

python
from utils import add, to_upper

Tip

Best Practice

Use __init__.py to expose a clean public API and hide internal details.

3) Absolute vs Relative Import

Absolute Import

python
from utils.math_tools import add

Relative Import (inside package modules)

python
from .math_tools import add

Rules of thumb:

  • use absolute imports in app entry files
  • use relative imports inside the same package when appropriate

4) Subpackages

Large projects often use nested package structures.

text
shop/
├── __init__.py
├── services/
│   ├── __init__.py
│   └── order_service.py
└── models/
    ├── __init__.py
    └── order.py

This keeps domains separated and improves maintainability.

5) Real Mini Example: Score Package

Build a package for score utilities.

Structure:

text
score_app/
├── main.py
└── score_utils/
    ├── __init__.py
    ├── calc.py
    └── level.py

score_utils/calc.py:

python
# Calculate average score
def average(scores):
    return sum(scores) / len(scores)

score_utils/level.py:

python
# Convert score to grade level
def grade(score):
    if score >= 90:
        return "A"
    if score >= 80:
        return "B"
    if score >= 70:
        return "C"
    return "D"

score_utils/__init__.py:

python
from .calc import average
from .level import grade

main.py:

python
from score_utils import average, grade
 
scores = [95, 82, 76]
avg = average(scores)
 
print(f"Average: {avg:.2f}")
print(f"Grade of first score: {grade(scores[0])}")

This shows how packages make multi-module projects clean and reusable.

6) Common Package Pitfalls

Module Not Found

Often caused by:

  • wrong working directory
  • broken import path
  • naming conflicts

Name Conflicts

Avoid file/package names that collide with standard library modules (for example json, random, os).

Circular Imports

If modules import each other directly, import errors may occur.

Warning

Do not over-split tiny scripts into too many packages.
Package structure should match project complexity.

Common Beginner Mistakes

Mistake 1: Missing or Misusing __init__.py

Package behavior can become unclear without a clear package entry design.

Mistake 2: Confusing Relative and Absolute Imports

This often causes import errors when running files from different locations.

Mistake 3: Running Internal Package File Directly

Executing deep internal files directly can break expected import context.

Surprise Practice Challenge

Build a "mini_blog" package:

  1. mini_blog/posts.py for post data functions
  2. mini_blog/users.py for user data functions
  3. mini_blog/__init__.py to expose key APIs
  4. main.py imports package APIs and prints a simple report
  5. Keep import paths clean and consistent

If you finish this, you are building code structure like real Python projects.

FAQ

What is the difference between module and package?

A module is one .py file; a package is a folder containing multiple modules.

Is __init__.py always required?

Modern Python supports namespace packages, but using __init__.py is still common and recommended for clarity.

Should I always use subpackages?

Only when project size or domain complexity justifies it.

Why does code run in IDE but fail in terminal import?

Usually because working directory and execution context differ, affecting module search paths.