Python Modules & Import System
In Python, a module is a file with a .py extension containing executable Python statements, function definitions, classes, and global variables. Modules are the fundamental architectural building block for code organization, reusability, and namespace encapsulation.
When Python encounters an import statement for the first time, it performs three operations behind the scenes:
- Locates the file: Searches through the directories configured in
sys.path. - Compiles to Bytecode: Parses source code into bytecode (.pyc) and caches it inside the
__pycache__/directory to accelerate future startups. - Executes and Caches in Memory: Executes the top-level statements from top to bottom, creates a new
moduletype object, and stores a reference to it in the globalsys.modulesdictionary table.
Because Python caches imported modules in sys.modules, importing the same module 100 times across 100 different files executes the module's initialization code only once!
# Inspecting module metadata and the sys.modules cache:
import math
import sys
print("Module Object:", math)
print("Module Name (__name__):", math.__name__)
print("Module Docstring:", math.__doc__[:60], "...")
print("Is 'math' cached in sys.modules?", 'math' in sys.modules)
mathis a built-in C-extension module compiled directly into the CPython binary.sys.modules['math']holds the live module object in memory. Subsequentimport mathcalls merely return this cached reference in $O(1)$ time.
Python provides three flexible syntax patterns to bring module members into your current namespace:
1. Standard import module_name:
Brings the entire module into your namespace as a qualified prefix (e.g. math.sqrt(16)). This is the safest approach because it avoids any risk of naming collisions with your local variables.
2. Specific from module_name import item1, item2:
Brings specific functions or classes directly into your local scope so you can call sqrt(16) without prefixing math..
3. Renaming Aliases with as:
Provides a concise or disambiguated shorthand name for lengthy module names (e.g. import datetime as dt or import numpy as np).
Wildcard imports dump all public identifiers into your local namespace. This causes silent variable shadowing, ruins IDE autocomplete and static type checkers, and makes tracking where a function came from nearly impossible.
# Method 1: Standard import with full qualification
import math
res1 = math.pow(2, 3)
# Method 2: Importing specific members directly
from math import sqrt, pi
res2 = sqrt(144)
# Method 3: Importing with clean alias
import datetime as dt
current_year = dt.datetime.now().year
print(f"2 ** 3 = {res1}")
print(f"Square root of 144 = {res2}")
print(f"Value of Pi = {pi:.4f}")
print(f"Current Year via 'dt' alias: {current_year}")
Method 1 keeps your namespace clean under math.*. Method 2 injects sqrt and pi directly. Method 3 simplifies access while preserving modular clarity.
When you execute import my_module, Python does not search your entire hard drive. Instead, it searches a precise, ordered list of directory path strings stored in sys.path:
- Current Directory: The directory containing the script that was executed from the terminal (or current working directory in interactive shells).
- PYTHONPATH Environment Variable: Any custom directory paths configured by the developer in the operating system environment.
- Standard Library Directories: The directory where official Python modules (like
math,json,os) are installed. - Site-Packages (Third-Party Packages): The directory where packages installed via
pip(likerequests,fastapi,pandas) live.
If Python searches all directories in sys.path without finding a matching .py file or compiled C extension, it halts and raises a ModuleNotFoundError: No module named '...' exception.
import sys
print("--- ๐ Python Module Search Paths (sys.path) ---")
for index, directory_path in enumerate(sys.path, start=1):
print(f"{index}. {directory_path}")
You can dynamically append new search folders at runtime using sys.path.append('/custom/path') if your project structure requires loading modules from external directories.
Every Python module has a built-in special variable named __name__ automatically set by the CPython interpreter:
- When you execute a file directly from the terminal (e.g.
python app.py), Python sets__name__ = "__main__". - When a file is imported into another script (e.g.
import app), Python sets__name__ = "app"(the actual module name).
This allows a Python file to act as both a reusable library of functions AND an executable standalone script with unit tests or CLI demos!
# geometry_helper.py
def calculate_circle_area(radius):
"""Reusable function for calculating circle area."""
import math
return math.pi * (radius ** 2)
def calculate_perimeter(radius):
"""Reusable function for calculating circumference."""
import math
return 2 * math.pi * radius
# The __name__ check ensures this test block only runs when executed directly:
if __name__ == "__main__":
print("--- ๐งช Running geometry_helper.py Standalone Tests ---")
test_r = 5
print(f"Test Radius: {test_r}")
print(f"Calculated Area: {calculate_circle_area(test_r):.2f}")
print(f"Calculated Perimeter: {calculate_perimeter(test_r):.2f}")
Without if __name__ == "__main__":, any testing code or prints would automatically execute and pollute the output whenever another developer writes import geometry_helper.
If you create a script named random.py or math.py in your project folder, Python's sys.path priority #1 (current directory) causes Python to import your empty file instead of the official standard library module, causing AttributeError: module 'random' has no attribute 'randint'.
Import the built-in math and statistics modules to compute the hypotenuse of a right triangle (math.hypot(3, 4)) and the mean of a list of numbers.
import math
import statistics as stats
hypotenuse = math.hypot(3, 4)
print("Hypotenuse (3, 4):", hypotenuse)
dataset = [10, 20, 30, 40, 50]
print("Dataset:", dataset)
print("Mean:", stats.mean(dataset))
Q What is the purpose of the __pycache__ directory?
__pycache__ stores compiled bytecode (.pyc files) generated by CPython. When you re-run a script without modifying its source code, Python skips parsing and compilation, loading cached bytecode directly.
Q What is the difference between a module and a script in Python?
A script is designed to run directly from the command line to perform a task. A module is designed to be imported into other files to provide reusable functions and classes.
Q Can I reload an imported module at runtime without restarting Python?
Yes! Use the standard library importlib module: import importlib; importlib.reload(my_module).