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)
- Modules — the unit of reuse
- How
importworks - Packages
- Absolute vs relative imports
__name__and__main____all__— public export list- Circular imports — problem and fixes
- Module search path patterns
- 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:
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:
- Is
fooalready insys.modules? → reuse. - Find
fooonsys.path. - Load and execute module top-level code.
- Cache in
sys.modules. - 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)
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
__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:
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:
Sets __name__ to "__main__" on main module and fixes import paths relative to package.
__all__ — public export list#
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):
2. Restructure — extract shared code to third module:
3. Import module, not names — use qualified access after both loaded:
4. TYPE_CHECKING block for type hints only:
Module search path patterns#
src layout (recommended)#
Install editable: pip install -e . — adds package to site-packages.
Flat layout (small scripts)#
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#
- When does module-level code run?
- What is stored in
sys.modules? - What does
if __name__ == "__main__"protect against? - How do relative imports count dot levels?
- 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 |