Free GuideIntermediate
35 min read
#python#modules#packages#imports#__init__#namespaces

Modules and Packages

Comprehensive guide on Modules and Packages.

Modules and Packages

Overview#

As programs grow, it becomes essential to split code across multiple files and organize related files into directories. Python calls a single file a module and a directory of related modules a package. This notebook covers creating and importing modules, the different import styles and when to use each, how packages and __init__.py work, relative vs absolute imports, the if __name__ == "__main__": pattern, and how Python actually finds modules when you import them.

1. What Is a Module?#

Any .py file is a module. Its name (used for importing) is the filename without the .py extension.

Python 3.12 (WASM)
# math_utils.py def add(a, b): return a + b def subtract(a, b): return a - b PI = 3.14159
Python 3.12 (WASM)
# main.py import math_utils print(math_utils.add(2, 3)) # 5 print(math_utils.PI) # 3.14159

When a module is imported, Python runs the entire file top to bottom once, and the resulting names (functions, variables, classes) become attributes accessible through the module name.

2. Different Import Styles#

Python 3.12 (WASM)
# Import the whole module, access members with a prefix import math_utils math_utils.add(1, 2) # Import specific names directly into the current namespace from math_utils import add, PI add(1, 2) # no prefix needed print(PI) # Import everything (generally discouraged) from math_utils import * add(1, 2) # works, but unclear where 'add' came from when reading code # Import with an alias import math_utils as mu mu.add(1, 2) from math_utils import add as sum_two sum_two(1, 2)

Why from module import * is discouraged#

Python 3.12 (WASM)
# module_a.py def process(): ... # module_b.py def process(): ... # same name! # main.py from module_a import * from module_b import * process() # which one runs? Whichever was imported LAST silently wins

Wildcard imports pollute the current namespace and make it unclear where a name came from, which hurts both readability and tooling (like autocomplete and static analysis). Prefer explicit imports, or import the module itself and use a prefix.

3. The Module Search Path#

When you import something, Python searches, in order:

  1. The directory of the script being run (or the current directory in interactive mode).
  2. Directories listed in the PYTHONPATH environment variable.
  3. Standard library installation directories.
  4. Site-packages directories (where pip-installed packages live).
Python 3.12 (WASM)
import sys print(sys.path) # the actual list of directories Python searches, in order

If a module can't be found, Python raises ModuleNotFoundError. This is often the cause of "why can't Python find my file" issues — the script's working directory or the module's location isn't on sys.path.

4. Module Caching: sys.modules#

A module is only executed the first time it's imported in a given program run — subsequent imports reuse the cached module object, they don't re-run the file.

Python 3.12 (WASM)
import sys import math_utils print("math_utils" in sys.modules) # True import math_utils # does NOT re-execute math_utils.py — it's already cached

This is why side effects at the top level of a module (like print() statements, or opening a database connection) only happen once, no matter how many times or from how many other files the module gets imported.

5. if __name__ == "__main__":#

Every module has a built-in __name__ attribute. When a file is run directly (python script.py), Python sets __name__ to "__main__". When the same file is imported by another module, __name__ is set to the module's actual name instead.

Python 3.12 (WASM)
# math_utils.py def add(a, b): return a + b def main(): print(add(2, 3)) print(add(10, 20)) if __name__ == "__main__": main()
bash
python math_utils.py # prints 5, 30 — main() runs because __name__ == "__main__"
Python 3.12 (WASM)
# main.py import math_utils # math_utils.py runs, but main() is NOT called here, # because __name__ inside math_utils.py is "math_utils", not "__main__"

This pattern lets a file work both as a reusable module (when imported) and as a standalone script (when run directly) — extremely common in real-world Python code, including almost every library's example/demo scripts.

6. Packages: Organizing Modules into Directories#

A package is a directory containing an __init__.py file (which can be empty), plus one or more modules.

Architecture & Data Flow
my_package/
├── __init__.py
├── math_utils.py
├── string_utils.py
└── validators/
    ├── __init__.py
    ├── email.py
    └── phone.py
Python 3.12 (WASM)
# Importing from a package from my_package import math_utils from my_package.string_utils import capitalize_words from my_package.validators.email import is_valid_email math_utils.add(1, 2) capitalize_words("hello world") is_valid_email("test@example.com")

The role of __init__.py#

__init__.py runs automatically when the package (or any module within it) is first imported. It can be empty, just marking the directory as a package, or it can control what gets exposed when someone imports the package itself.

Python 3.12 (WASM)
# my_package/__init__.py from .math_utils import add, subtract from .string_utils import capitalize_words __all__ = ["add", "subtract", "capitalize_words"]

With this __init__.py, users can do:

Python 3.12 (WASM)
from my_package import add, capitalize_words # works because of __init__.py's re-exports

instead of needing to know the internal module structure (my_package.math_utils.add).

__all__ controls exactly what names are imported when someone uses from my_package import * — only names listed there are exposed that way.

7. Relative vs Absolute Imports#

Absolute imports specify the full path from the project's root package. Relative imports use dots to reference modules relative to the current module's location, and only work inside a package.

Python 3.12 (WASM)
# my_package/validators/email.py # Absolute import — full path from the top-level package from my_package.string_utils import capitalize_words # Relative import — "..") goes up one package level from ..string_utils import capitalize_words # Relative import — "." refers to the current package from . import phone
SyntaxMeaning
from . import xImport x from the current package
from .module import xImport x from a sibling module in the same package
from .. import xImport x from the parent package
from ..module import xImport x from a module in the parent package

Absolute imports are generally preferred for clarity and are recommended by Python's own style guide (PEP 8), except in cases where relative imports meaningfully reduce coupling to the top-level package name within a large, self-contained package.

8. Reloading a Module#

Since modules are cached after the first import, changes to a module's source won't be reflected in an already-running session unless explicitly reloaded — mainly relevant in interactive sessions or notebooks.

Python 3.12 (WASM)
import importlib import math_utils importlib.reload(math_utils) # re-executes math_utils.py, updating the cached module

This is rarely needed in normal scripts (which start fresh each run), but is common when iterating in a long-running interactive session.

9. Organizing a Real Project#

A typical, well-organized project layout:

Architecture & Data Flow
my_project/
├── src/
│   └── my_project/
│       ├── __init__.py
│       ├── main.py
│       ├── config.py
│       └── utils/
│           ├── __init__.py
│           ├── validation.py
│           └── formatting.py
├── tests/
│   ├── test_validation.py
│   └── test_formatting.py
├── requirements.txt
└── README.md

Placing the actual package inside a src/ directory (the "src layout") is a widely recommended convention — it avoids accidentally importing uninstalled local code instead of the properly installed package, which can hide packaging bugs during development.

Notes#

  • A module's top-level code (outside of function/class definitions) runs exactly once, the first time it's imported, and is cached afterward.
  • import module_name never re-runs the module's code on subsequent imports within the same program — this is important to remember when a module has side effects like opening a file or connecting to a database.
  • __name__ == "__main__" is checked at the bottom of a script, not the top, so that all functions and classes are defined before the check runs.
  • Circular imports (module A imports module B, which imports module A) are a common source of ImportError in larger projects — usually resolved by restructuring shared code into a separate module both can import from.
  • sys.path can be modified at runtime (sys.path.append(...)), but this is generally a sign that a project's structure or installation needs fixing rather than a good permanent solution.

Best Practices Checklist#

  • Avoid from module import * — prefer explicit imports or a module prefix
  • Use if __name__ == "__main__": to separate reusable code from script-only execution logic
  • Keep __init__.py files focused on re-exporting a clean public interface, not heavy logic
  • Prefer absolute imports for clarity, reserving relative imports for tightly coupled code within the same package
  • Define __all__ in __init__.py when you want to control exactly what's exposed via wildcard imports
  • Avoid circular imports by extracting shared logic into its own module
  • Use a src/ layout for real projects to avoid accidental imports of uninstalled local code
  • Remember modules are only executed once per program run — avoid relying on re-execution for state resets
Knowledge Checkpoint

Modules & Package Architecture Checkpoint

Q1.What is the primary purpose of the `if __name__ == '__main__':` boilerplate in a Python script?
ATo allow code inside the block to run only when the script is executed directly, and not when imported as a module.
BTo declare the script as the entry point for CPython bytecode caching.
CTo grant administrator/root permissions to the executing script.
DTo enable multi-threading on the main interpreter thread.
Q2.Where does Python search for imported modules when an `import` statement is executed?
AOnly in the current directory.
BIn the paths listed in `sys.path` (starting with script directory, `PYTHONPATH`, and installed site-packages).
CIn the Windows Registry or `/etc/python` directories.
DIn the active user's Desktop folder.
Q3.What is the purpose of `__all__` defined in a package's `__init__.py`?
AIt restricts which users can read the file.
BIt defines the public symbols exported when a consumer uses `from module import *`.
CIt deletes private variables from memory after import.
DIt automatically compiles the module into a `.so` binary.
Track Your Learning

Finished studying this notebook?

Mark this guide as completed to update your course progress roadmap.