Gyaan

Modules, Packages, and Imports

beginner modules packages imports init

A module is simply a .py file. When we write import math, Python finds the file math.py (or a built-in equivalent) and makes its contents available to us. That’s it — every Python file we create is already a module.

Importing Modules

There are a few ways to bring code from one file into another.

import math                    # import the whole module
print(math.sqrt(16))           # 4.0 — access with module.name

from math import sqrt, pi     # import specific names
print(sqrt(16))                # 4.0 — no prefix needed

from math import sqrt as s    # alias to avoid name clashes
print(s(16))                   # 4.0

Avoid from math import * — it dumps everything into our namespace and we lose track of where names come from.

Packages

A package is a folder of modules. It groups related files together. Python recognizes a folder as a package if it contains an __init__.py file (can be empty).

myapp/
    __init__.py        # makes myapp a package
    utils.py
    models/
        __init__.py    # makes models a sub-package
        user.py
from myapp.utils import helper_func
from myapp.models.user import User

__init__.py

This file runs when the package is imported. We can use it to expose a clean public API.

# myapp/__init__.py
from .utils import helper_func   # re-export for convenience
__all__ = ["helper_func"]        # controls what `from myapp import *` exports

Absolute vs Relative Imports

  • Absolute imports spell out the full path from the project root: from myapp.models.user import User
  • Relative imports use dots to refer to the current package: from .utils import helper_func (one dot = current package, two dots = parent)

Absolute imports are clearer and preferred in most cases. Relative imports are handy inside a package to avoid repeating long paths.

Circular Imports

This happens when module A imports module B, and module B imports module A. Python gets stuck in a loop.

# a.py
from b import greet    # tries to load b.py, which tries to load a.py...

# b.py
from a import name     # circular!

Fixes: move the shared code into a third module, use lazy imports (import inside a function instead of at the top), or restructure so the dependency goes one way.

if __name__ == "__main__"

Every module has a __name__ attribute. When we run a file directly, __name__ is set to "__main__". When it’s imported, __name__ is the module’s actual name.

# utils.py
def add(a, b):
    return a + b

if __name__ == "__main__":
    # this block only runs when we execute: python utils.py
    print(add(2, 3))  # 5 — great for quick testing

How Python Finds Modules

When we write import something, Python searches in this order:

  1. The current directory
  2. Built-in modules
  3. Directories listed in sys.path (which includes installed packages)
import sys
print(sys.path)  # list of directories Python searches

In simple language, modules are just files, packages are just folders, and imports are how we connect them. Python’s import system is straightforward once we see that everything is just files on disk.