Skip to main content

Python Modules & Packages

Fundamentals Deep Dive

Python Modules & Packages

Break large projects into maintainable modules and packages. Master the import system, virtual environments, and packaging basics so your code scales cleanly.

Why modularity matters

  • Keeps files short and focused
  • Enables reuse and testing
  • Unlocks packaging/distribution via pip

Imports in practice

import math
from datetime import datetime as dt

from app.payments import processor
  • Use absolute imports for clarity.
  • Avoid wildcard imports (from module import *).
  • Use __all__ to control export surface.

Module search path

Python searches in this order:

  1. Directory containing the script/REPL session.
  2. Paths listed in PYTHONPATH.
  3. Standard library directories.
  4. Site-packages for the active environment.

Inspect with:

import sys
print(sys.path)

Package structure

payments/
├── payments/__init__.py
├── payments/processors.py
├── payments/utils.py
├── tests/
│ └── test_processors.py
└── pyproject.toml
  • __init__.py marks a directory as a package.
  • Use pyproject.toml to define metadata and dependencies.

Relative imports

from .utils import sanitize
from ..core.config import settings

Use sparingly inside packages; prefer absolute imports for readability unless reorganizing submodules frequently.

Publishing basics

  1. Add metadata to pyproject.toml (name, version, dependencies).
  2. Build with python -m build.
  3. Upload to PyPI via twine.

For internal packages, push to a private index or install from VCS directly.

Next up in your learning path

Frequently Asked Questions

Do I still need `__init__.py` on Python 3?

Implicit namespace packages work without `__init__.py`, but adding the file keeps behavior consistent and lets you control what gets exported.

What's the difference between a module and a package?

A module is a single `.py` file. A package is a directory containing modules (and usually an `__init__.py`). Packages are how you distribute groups of modules.

How do I avoid circular imports?

Refactor shared logic into separate modules, delay imports inside functions, or reorganize dependencies. Excessive circular imports signal a design issue.