Skip to content

Modules and Packages#

Python code lives in modules (files) and packages (directories). Understanding imports prevents ModuleNotFoundError, circular import bugs, and accidental side effects — especially in take-home projects and multi-file interview templates.

How to use this page

Pair with The Python Environment. Advanced packaging: Creating Modules and Packages.

At a glance
Track Python Intermediate
Sections 9 major topics
Outline Use the right-hand TOC to jump

Topics: Modules — the unit of reuse · How import works · Packages · Absolute vs relative imports · __name__ and __main__ · __all__ — public export list · Circular imports — problem and fixes · Module search path patterns · … (+1 more)

  1. Modules — the unit of reuse
  2. How import works
  3. Packages
  4. Absolute vs relative imports
  5. __name__ and __main__
  6. __all__ — public export list
  7. Circular imports — problem and fixes
  8. Module search path patterns
  9. Standard library module categories (mental map)

Modules — the unit of reuse#

A .py file is a module. Its name (without .py) is the module name:

project/
├── main.py
└── utils.py
# main.py
import utils
utils.helper()

from utils import helper
helper()

When imported, the module body runs once (first import). Subsequent imports reuse the cached module in sys.modules.


How import works#

import math                 # binds name 'math' to module object
from math import sqrt       # binds 'sqrt' into current namespace
from math import sqrt as root
import json as js

Execution order for import foo:

  1. Is foo already in sys.modules? → reuse.
  2. Find foo on sys.path.
  3. Load and execute module top-level code.
  4. Cache in sys.modules.
  5. Bind name(s) in caller namespace.

Where Python looks (sys.path)#

import sys
sys.path
# Typically:
# 1. Directory containing the script (or '' for REPL)
# 2. PYTHONPATH directories
# 3. Standard library
# 4. site-packages (venv packages)
PYTHONPATH=src python3 -m myapp.main

Prefer proper package layout over mutating sys.path in library code.


Packages#

A package is a directory containing __init__.py (traditional) or a namespace package (PEP 420 — directory without __init__.py on sys.path).

myproject/
├── pyproject.toml
└── src/
    └── mypackage/
        ├── __init__.py
        ├── core.py
        └── utils/
            ├── __init__.py
            └── helpers.py
from mypackage.core import solve
from mypackage.utils.helpers import format_output

__init__.py roles#

# mypackage/__init__.py
"""Public API for mypackage."""

from .core import solve
from .utils.helpers import format_output

__all__ = ["solve", "format_output"]
__version__ = "1.0.0"
Purpose Example
Mark package Empty file is valid
Re-export API from .core import solve
Package-level init Setup logging, constants
Control from pkg import * __all__ list

Absolute vs relative imports#

Absolute — from project/package root on sys.path:

from mypackage.utils.helpers import helper

Relative — within same package (dots = levels up):

# In mypackage/utils/helpers.py
from ..core import solve      # up to mypackage, then core
from . import submod          # same package

Relative imports require the module to be part of a package — not when running a file directly as script in some layouts.

Rule: use absolute imports in application code; relative imports inside large packages to avoid name clashes.


__name__ and __main__#

# utils.py
def helper():
    return 42

if __name__ == "__main__":
    # Runs only when: python3 utils.py
    # NOT when: import utils
    print(helper())

Recommended entry point for packages:

python3 -m mypackage.main

Sets __name__ to "__main__" on main module and fixes import paths relative to package.


__all__ — public export list#

# utils.py
__all__ = ["helper", "Parser"]

def helper(): ...
def _internal(): ...
class Parser: ...
from utils import *   # imports only names in __all__

Explicit is better than import * in production — but __all__ documents the public API.


Circular imports — problem and fixes#

Problem: a.py imports b, b.py imports a — partial initialization, AttributeError.

# a.py
from b import func_b
def func_a(): return func_b()

# b.py
from a import func_a   # circular
def func_b(): return 1

Fixes (multiple approaches)#

1. Move import inside function (lazy import):

def func_a():
    from b import func_b
    return func_b()

2. Restructure — extract shared code to third module:

common.py  ← shared types/constants
a.py       ← imports common
b.py       ← imports common

3. Import module, not names — use qualified access after both loaded:

import b
def func_a():
    return b.func_b()

4. TYPE_CHECKING block for type hints only:

from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from a import ClassA

Module search path patterns#

project/
├── src/
│   └── mypkg/
│       ├── __init__.py
│       └── main.py
├── tests/
└── pyproject.toml

Install editable: pip install -e . — adds package to site-packages.

Flat layout (small scripts)#

project/
├── main.py
└── utils.py

Run from project/ directory so utils is found.


Standard library module categories (mental map)#

Category Examples
Text/binary I/O io, pathlib, json, csv
OS / paths os, pathlib, shutil, glob
Data structures collections, heapq, bisect
Algorithms itertools, functools, operator
Time datetime, time
Networking urllib, http
Concurrency threading, multiprocessing, asyncio
Testing unittest, doctest

Details: Data Handling and Standard Library.


Interview traps (quick reference)#

Trap What goes wrong Safe approach
Wrong working directory ModuleNotFoundError Run from project root or python -m
Name shadowing stdlib json.py breaks import json Never name files after stdlib
Side effects on import Network/file I/O at import time Guard with if __name__
Circular imports Partial init errors Lazy import or refactor
sys.path hack Fragile deployments Proper package install
Relative import in script ImportError Use -m or absolute

Mental model checklist#

  1. When does module-level code run?
  2. What is stored in sys.modules?
  3. What does if __name__ == "__main__" protect against?
  4. How do relative imports count dot levels?
  5. What are three ways to break circular imports?

What's next#

Topic Page
Environment setup The Python Environment
File I/O modules File Handling
Advanced packaging Creating Modules and Packages
Stdlib overview Data Handling and Standard Library