Modules & Packages

0/4 in this phase0/54 across the roadmap

📖 Concept

Python's module system is how you organize code into reusable, maintainable units. A module is a single .py file. A package is a directory containing modules and an __init__.py file (optional since Python 3.3 for namespace packages, but recommended).

Import types:

import math                    # Import entire module
from math import sqrt, pi      # Import specific names
from math import *             # Import all (avoid in production!)
import numpy as np             # Alias
from os.path import join as pjoin  # Import + alias

How Python finds modules (in order):

  1. Current directory (or script's directory)
  2. PYTHONPATH environment variable directories
  3. Standard library directories
  4. site-packages (pip-installed packages)

This search order is stored in sys.path and can be modified at runtime.

Package structure:

mypackage/
├── __init__.py       # Makes it a package, runs on import
├── module_a.py
├── module_b.py
└── subpackage/
    ├── __init__.py
    └── module_c.py

Important files:

  • __init__.py — Executes when the package is imported. Used to expose a clean public API.
  • __main__.py — Executes when the package is run with python -m package_name.
  • __all__ — List of names exported by from module import *.

Best practices:

  • Use absolute imports (from mypackage.module import func) over relative imports
  • Keep __init__.py minimal — just re-exports, not heavy logic
  • Use if __name__ == "__main__": guard for executable scripts

💻 Code Example

codeTap to expand ⛶
1# ============================================================
2# Module basics
3# ============================================================
4# File: mathutils.py
5"""Math utility functions."""
6
7PI = 3.14159265358979
8
9def circle_area(radius):
10 """Calculate the area of a circle."""
11 return PI * radius ** 2
12
13def factorial(n):
14 """Calculate n factorial."""
15 if n <= 1:
16 return 1
17 return n * factorial(n - 1)
18
19# This block only runs when the file is executed directly,
20# NOT when it's imported as a module
21if __name__ == "__main__":
22 print(f"Pi: {PI}")
23 print(f"Area of circle with r=5: {circle_area(5)}")
24 print(f"10! = {factorial(10)}")
25
26# ============================================================
27# Import styles
28# ============================================================
29# In another file:
30# import mathutils
31# print(mathutils.circle_area(5))
32
33# from mathutils import circle_area, PI
34# print(circle_area(5))
35
36# import mathutils as mu
37# print(mu.circle_area(5))
38
39# ============================================================
40# Package structure
41# ============================================================
42
43# myapp/
44# ├── __init__.pyPackage initializer
45# ├── __main__.pyRun with: python -m myapp
46# ├── core/
47# │ ├── __init__.py
48# │ ├── models.py
49# │ └── database.py
50# ├── api/
51# │ ├── __init__.py
52# │ ├── routes.py
53# │ └── middleware.py
54# └── utils/
55# ├── __init__.py
56# ├── helpers.py
57# └── validators.py
58
59# ============================================================
60# __init__.pypackage API
61# ============================================================
62
63# myapp/__init__.py
64# """MyApp — A sample application."""
65# __version__ = "1.0.0"
66#
67# # Re-export commonly used names for clean imports
68# from myapp.core.models import User, Product
69# from myapp.core.database import connect
70#
71# __all__ = ["User", "Product", "connect"]
72
73# Users can then do:
74# from myapp import User, connect
75# Instead of:
76# from myapp.core.models import User
77
78# ============================================================
79# __main__.pypackage as script
80# ============================================================
81
82# myapp/__main__.py
83# """Entry point when running: python -m myapp"""
84# from myapp.core.database import connect
85#
86# def main():
87# db = connect()
88# print(f"Connected to {db}")
89#
90# if __name__ == "__main__":
91# main()
92
93# ============================================================
94# Relative imports (within a package)
95# ============================================================
96
97# In myapp/api/routes.py:
98# from . import middleware # Same package
99# from ..core import models # Parent's sibling package
100# from ..utils.helpers import slugify # Specific function
101
102# NOTE: Relative imports only work inside packages.
103# Running a file directly (python routes.py) won't work with
104# relative imports. Use: python -m myapp.api.routes
105
106# ============================================================
107# sys.path and module resolution
108# ============================================================
109import sys
110
111# View the search path
112for path in sys.path:
113 print(path)
114
115# Add a custom path (useful for development)
116# sys.path.insert(0, '/path/to/my/modules')
117
118# Check where a module is loaded from
119import json
120print(json.__file__) # Path to json module
121print(json.__name__) # 'json'
122print(json.__package__) # 'json'
123
124# ============================================================
125# Module introspection
126# ============================================================
127import os
128
129# List all names in a module
130print(dir(os))
131
132# Get module docstring
133print(os.__doc__[:200])
134
135# Check if a name exists in a module
136print(hasattr(os, 'getcwd')) # True
137
138# Dynamically import a module
139import importlib
140json_module = importlib.import_module('json')
141print(json_module.dumps({"key": "value"}))
142
143# ============================================================
144# __all__ — controlling star imports
145# ============================================================
146
147# In mymodule.py:
148# __all__ = ["public_func", "PublicClass"]
149#
150# def public_func():
151# pass
152#
153# def _private_func():
154# pass # Not exported by star import (convention)
155#
156# class PublicClass:
157# pass
158
159# from mymodule import * → only imports public_func and PublicClass
160
161# ============================================================
162# if __name__ == "__main__" pattern
163# ============================================================
164def main():
165 """Main entry point."""
166 print("Running as script")
167
168# This guard is critical!
169# Without it, the code below would run on IMPORT too
170if __name__ == "__main__":
171 main()
172
173# When imported: __name__ == "mathutils" (module name)
174# When run directly: __name__ == "__main__"

🏋️ Practice Exercise

Exercises:

  1. Create a small package called calculator with modules for basic.py (add, subtract, multiply, divide), scientific.py (sqrt, power, log), and an __init__.py that re-exports the most common functions.

  2. Add a __main__.py to your calculator package so it can be run with python -m calculator as an interactive calculator.

  3. Explore sys.path: print all directories Python searches for modules. Add a custom directory and import a module from it.

  4. Create a module with __all__ defined. Demonstrate the difference between from module import * with and without __all__.

  5. Write a function lazy_import(module_name) that dynamically imports a module using importlib only when first accessed. This is useful for optional dependencies.

  6. Demonstrate the difference between absolute and relative imports in a package with nested subpackages.

⚠️ Common Mistakes

  • Circular imports — module A imports module B which imports module A. Fix by restructuring code, using local imports (inside functions), or extracting shared code to a third module.

  • Running a package file directly (python mypackage/module.py) instead of as a module (python -m mypackage.module). Direct execution breaks relative imports.

  • Using from module import * in production code — it pollutes the namespace, makes it unclear where names come from, and can cause silent shadowing of existing names.

  • Not understanding if __name__ == '__main__': — code outside this guard runs on both import AND direct execution. Tests, print statements, and initialization code should be inside the guard.

  • Naming your module the same as a standard library module (e.g., creating json.py or email.py in your project). This shadows the built-in module and causes confusing import errors.

💼 Interview Questions

🎤 Mock Interview

Practice a live interview for Modules & Packages