A module is a single file of Python code that can be imported; a package is a directory of modules identified to Python as importable. The import system, defined in the Python Language Reference, locates modules via sys.path, executes them once, and binds their names into the importing namespace.
Importing code and organising your projects
What you'll learn: How to import modules from the standard library, how Python packages work, and how to install third-party packages with pip in a virtual environment.
How to read this tab: After reading the import section, open the REPL and practice: import math, from datetime import date, import json as j. The syntax is simple — the understanding comes from doing.
A module is just a .py file. When you import it, you get access to the functions, classes, and variables it defines. A package is a folder of modules. This is how Python code is organised and shared — your own code, the standard library, and third-party libraries all work the same way.
Four import forms to know: 1) import module — use as module.thing. 2) from module import thing — use as thing directly. 3) import module as alias — common for long names (import numpy as np). 4) from module import thing as alias. Avoid from module import * — it pollutes your namespace and makes it unclear where names come from.
The import statement
There are a few forms of import. Each one runs the target module once (the first time) and binds names so you can use them. The Python Tutorial describes a module as a file containing Python definitions and statements, with the file name being the module name plus .py.
# Import the whole module — access names with module.name
import math
print(math.sqrt(16)) # 4.0
print(math.pi) # 3.141592653589793
# Import specific names from a module
from math import sqrt, pi
print(sqrt(16)) # 4.0 — no 'math.' prefix needed
# Import with an alias (very common for long names)
import datetime as dt
now = dt.datetime.now()
# Import a name with an alias
from collections import OrderedDict as OD
# Importing your own module — a file called helpers.py
# helpers.py defines: def greet(name): return f"Hi {name}"
import helpers
print(helpers.greet("Priya")) # Hi Priya
from helpers import greet
print(greet("Arjun")) # Hi Arjunfrom module import *The form from module import * imports every public name into your namespace. It is discouraged because it makes it unclear where names came from and can silently overwrite existing names. Import only what you need, or import the module and use its prefix.
Making a directory importable. A package is a directory with an __init__.py file. That file runs when the package is first imported — use it to expose a clean public API. If __init__.py contains from .cart import add_item, users can write from store import add_item instead of from store.cart import add_item.
Packages and __init__.py
A package is a directory containing modules. Historically, a directory became a package by including an __init__.py file (which may be empty). That file runs when the package is first imported and can expose a curated public API. The Python Tutorial explains that the __init__.py files are required to make Python treat directories containing the file as packages (for regular packages).
myproject/
main.py
store/ <- a package (a directory)
__init__.py <- marks 'store' as a package
cart.py <- a module: store.cart
payment.py <- a module: store.payment
utils/ <- a sub-package
__init__.py
format.py <- store.utils.format# From main.py — import from the package
import store.cart
store.cart.add_item("book")
from store import payment
payment.charge(500)
from store.utils.format import to_currency
print(to_currency(500)) # ₹500.00
# __init__.py can re-export names for a clean public API.
# If store/__init__.py contains: from .cart import add_item
# then users can simply write:
from store import add_item✅ Beginner tab complete — check your understanding
- I can import a module and use its functions with module.function() syntax
- I can use "from module import name" to import specific names
- I understand what a package is (a directory of modules with __init__.py)
- I can create a virtual environment with python -m venv .venv and install packages with pip
sys.path, the __main__ guard, and relative imports
What you'll learn: How Python searches for modules (sys.path), the if __name__ == "__main__": guard and why it matters, and relative imports within packages.
How to read this tab: The __main__ guard is one of the most seen patterns in Python code. Make sure you understand exactly when it runs and when it doesn't.
Why "module not found" happens. Python searches sys.path in order: the script's directory, PYTHONPATH, then site-packages. If your module isn't in any of those places, you get ModuleNotFoundError. sys.modules is the cache — once a module is imported, subsequent imports just look it up there.
The module search path (sys.path)
When you import a module, Python searches a list of locations in order, stored in sys.path. The Python Language Reference specifies that the search begins with built-in modules, then proceeds through the directories in sys.path: the directory of the input script (or the current directory), then PYTHONPATH environment variable directories, then installation-dependent defaults (including site-packages where third-party libraries live).
import sys
# See exactly where Python looks for modules, in order
for path in sys.path:
print(path)
# sys.modules is a cache of already-imported modules.
# A module is executed only ONCE; later imports reuse the cache.
import math
print("math" in sys.modules) # True
# The name of the current module
print(__name__)
# In the file you run directly, __name__ == "__main__"
# In an imported module, __name__ is the module's nameif __name__ == "__main__" guardBecause an imported module's code runs on import, you wrap "run this only when executed directly" code in a guard. When the file is imported, __name__ is the module name; when run directly, it is "__main__".
# calculator.py
def add(a, b):
return a + b
def main():
print(add(2, 3))
# This block runs ONLY when the file is executed directly:
# python calculator.py -> runs main()
# import calculator -> does NOT run main()
if __name__ == "__main__":
main()Use a virtual environment for every project. Without one, pip installs packages into your system Python — and different projects needing different versions will conflict. The workflow: python -m venv .venv → activate → pip install. Every professional Python developer uses virtual environments. Make it a habit from day one.
Virtual environments and pip
Third-party packages are installed with pip, the package installer for Python, from the Python Package Index (PyPI). A virtual environment is an isolated Python environment with its own set of installed packages, created with the standard-library venv module. The Python documentation recommends using a virtual environment per project so dependencies do not conflict between projects.
# Create a virtual environment named .venv
python -m venv .venv
# Activate it
source .venv/bin/activate # macOS / Linux
.venv\Scripts\activate # Windows
# Now pip installs into THIS environment only
pip install requests
pip install "django>=5.0"
# List installed packages
pip list
# Save the exact dependency versions to a file
pip freeze > requirements.txt
# Recreate the environment elsewhere from that file
pip install -r requirements.txt
# Leave the virtual environment
deactivate.py file. A package is a directory of modules (traditionally with an __init__.py). Both are imported with import — the difference is structure, not syntax.import runs the module once. The first import executes the module top to bottom; subsequent imports reuse the cached module object from sys.modules. Top-level side effects (like print statements) happen only on that first import.venv creates an isolated environment; pip installs packages into whichever environment is active. Always activate a virtual environment before pip install so packages do not pollute your system Python.✅ Intermediate tab complete — check your understanding
- I know what sys.path is and in what order Python searches it
- I understand that modules are executed once and then cached in sys.modules
- I know that if __name__ == "__main__" runs only when the file is executed directly, not when imported
- I can use relative imports (from .module import name) within a package
The import protocol, importlib, and namespace packages
What you'll learn: The full import protocol — finders and loaders. importlib for dynamic imports and reload(). Namespace packages (PEP 420) — directories without __init__.py. How to debug circular imports.
How to read this tab: Read when you're building a library, framework, or plugin system that needs to control how modules are found and loaded.
Absolute vs relative: Absolute imports spell out the full path from the project root (preferred in most cases). Relative imports use dots: from .payment import charge (same package), from ..config import SETTINGS (parent package). Relative imports only work when the file is imported as part of a package — if you run it directly as a script, you get ImportError.
Absolute vs relative imports inside packages
Within a package, modules can import sibling modules using absolute imports (the full path from the project root) or relative imports (using leading dots to indicate the current and parent packages). PEP 328 introduced explicit relative imports. The Python documentation recommends absolute imports for clarity, but relative imports are useful within large packages. A single dot means the current package; two dots means the parent package.
# Inside store/cart.py
# Absolute import — full path from the project root (preferred)
from store.payment import charge
from store.utils.format import to_currency
# Relative import — dots indicate current/parent package
from .payment import charge # . = current package (store)
from .utils.format import to_currency
from ..config import SETTINGS # .. = parent package
# Relative imports only work inside a package being imported,
# NOT in a module run directly as a script. Running a file with
# relative imports directly raises:
# ImportError: attempted relative import with no known parent package
# Run it as a module instead: python -m store.cartHow import actually works: Python's import system has two stages — finders (locate the module) and loaders (execute it and build the module object). The whole thing is implemented in importlib, which you can use directly for dynamic imports: importlib.import_module("math"). importlib.reload() re-executes a module — useful in the REPL during development.
The import system internals and importlib
The import statement is implemented by importlib. The machinery has two stages: finders locate a module and return a loader; the loader executes the module and populates its namespace. The result is cached in sys.modules. This system is extensible — you can import from zip files, URLs, or custom sources by registering finders on sys.meta_path. The Python Language Reference documents this as the import protocol.
import importlib
# Import a module whose name is only known at runtime (a string)
module_name = "math"
math = importlib.import_module(module_name)
print(math.sqrt(25)) # 5.0
# Reload a module that has changed (rarely needed, useful in REPL)
import mymodule
importlib.reload(mymodule)
# Check if a module is available without importing it
spec = importlib.util.find_spec("numpy")
if spec is not None:
import numpy
else:
print("numpy not installed")Since Python 3.3 (PEP 420), a directory without an __init__.py can still be a "namespace package" — a package whose contents may be split across multiple directories on sys.path. This is an advanced feature used by large frameworks to let separate distributions contribute to the same package name. For ordinary projects, including __init__.py (a regular package) remains the clear, recommended choice.
If module A imports module B while B imports A, you can hit a circular import — one module sees the other only partially initialised, often raising ImportError or AttributeError. The fixes: restructure to remove the cycle, move the import inside the function that needs it (deferring it until call time), or import the module object rather than names from it.
✅ Expert tab complete
- I can use importlib.import_module() to import a module whose name is only known at runtime
- I know the difference between a regular package (__init__.py) and a namespace package (no __init__.py)
- I can diagnose and fix a circular import by restructuring or deferring the import