Modules & Packages
📖 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):
- Current directory (or script's directory)
- PYTHONPATH environment variable directories
- Standard library directories
- 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 withpython -m package_name.__all__— List of names exported byfrom module import *.
Best practices:
- Use absolute imports (
from mypackage.module import func) over relative imports - Keep
__init__.pyminimal — just re-exports, not heavy logic - Use
if __name__ == "__main__":guard for executable scripts
💻 Code Example
1# ============================================================2# Module basics3# ============================================================4# File: mathutils.py5"""Math utility functions."""67PI = 3.1415926535897989def circle_area(radius):10 """Calculate the area of a circle."""11 return PI * radius ** 21213def factorial(n):14 """Calculate n factorial."""15 if n <= 1:16 return 117 return n * factorial(n - 1)1819# This block only runs when the file is executed directly,20# NOT when it's imported as a module21if __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)}")2526# ============================================================27# Import styles28# ============================================================29# In another file:30# import mathutils31# print(mathutils.circle_area(5))3233# from mathutils import circle_area, PI34# print(circle_area(5))3536# import mathutils as mu37# print(mu.circle_area(5))3839# ============================================================40# Package structure41# ============================================================4243# myapp/44# ├── __init__.py ← Package initializer45# ├── __main__.py ← Run with: python -m myapp46# ├── core/47# │ ├── __init__.py48# │ ├── models.py49# │ └── database.py50# ├── api/51# │ ├── __init__.py52# │ ├── routes.py53# │ └── middleware.py54# └── utils/55# ├── __init__.py56# ├── helpers.py57# └── validators.py5859# ============================================================60# __init__.py — package API61# ============================================================6263# myapp/__init__.py64# """MyApp — A sample application."""65# __version__ = "1.0.0"66#67# # Re-export commonly used names for clean imports68# from myapp.core.models import User, Product69# from myapp.core.database import connect70#71# __all__ = ["User", "Product", "connect"]7273# Users can then do:74# from myapp import User, connect75# Instead of:76# from myapp.core.models import User7778# ============================================================79# __main__.py — package as script80# ============================================================8182# myapp/__main__.py83# """Entry point when running: python -m myapp"""84# from myapp.core.database import connect85#86# def main():87# db = connect()88# print(f"Connected to {db}")89#90# if __name__ == "__main__":91# main()9293# ============================================================94# Relative imports (within a package)95# ============================================================9697# In myapp/api/routes.py:98# from . import middleware # Same package99# from ..core import models # Parent's sibling package100# from ..utils.helpers import slugify # Specific function101102# NOTE: Relative imports only work inside packages.103# Running a file directly (python routes.py) won't work with104# relative imports. Use: python -m myapp.api.routes105106# ============================================================107# sys.path and module resolution108# ============================================================109import sys110111# View the search path112for path in sys.path:113 print(path)114115# Add a custom path (useful for development)116# sys.path.insert(0, '/path/to/my/modules')117118# Check where a module is loaded from119import json120print(json.__file__) # Path to json module121print(json.__name__) # 'json'122print(json.__package__) # 'json'123124# ============================================================125# Module introspection126# ============================================================127import os128129# List all names in a module130print(dir(os))131132# Get module docstring133print(os.__doc__[:200])134135# Check if a name exists in a module136print(hasattr(os, 'getcwd')) # True137138# Dynamically import a module139import importlib140json_module = importlib.import_module('json')141print(json_module.dumps({"key": "value"}))142143# ============================================================144# __all__ — controlling star imports145# ============================================================146147# In mymodule.py:148# __all__ = ["public_func", "PublicClass"]149#150# def public_func():151# pass152#153# def _private_func():154# pass # Not exported by star import (convention)155#156# class PublicClass:157# pass158159# from mymodule import * → only imports public_func and PublicClass160161# ============================================================162# if __name__ == "__main__" pattern163# ============================================================164def main():165 """Main entry point."""166 print("Running as script")167168# This guard is critical!169# Without it, the code below would run on IMPORT too170if __name__ == "__main__":171 main()172173# When imported: __name__ == "mathutils" (module name)174# When run directly: __name__ == "__main__"
🏋️ Practice Exercise
Exercises:
Create a small package called
calculatorwith modules forbasic.py(add, subtract, multiply, divide),scientific.py(sqrt, power, log), and an__init__.pythat re-exports the most common functions.Add a
__main__.pyto your calculator package so it can be run withpython -m calculatoras an interactive calculator.Explore
sys.path: print all directories Python searches for modules. Add a custom directory and import a module from it.Create a module with
__all__defined. Demonstrate the difference betweenfrom module import *with and without__all__.Write a function
lazy_import(module_name)that dynamically imports a module usingimportlibonly when first accessed. This is useful for optional dependencies.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.pyoremail.pyin your project). This shadows the built-in module and causes confusing import errors.
💼 Interview Questions
🎤 Mock Interview
Practice a live interview for Modules & Packages