Modules and Packages

This section documents the current Python release line as published at the official Python documentation, which is the reference these pages are written and verified against. No specific patch version is pinned.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

A module is simply a .py file; a package is a directory of modules. Splitting code this way lets it be reused across scripts and projects, as covered in the tutorial’s Modules chapter.

Importing Modules

import name runs the module’s top-level code once and binds the module object itself — its functions, classes, and variables are then reached as attributes:

import math

math.sqrt(16)      # 4.0
math.pi            # 3.141592653589793

from module import name binds specific names directly into the current namespace, skipping the module-object prefix; import module as alias renames the bound module object:

from math import sqrt, pi

sqrt(16)            # 4.0 -- no 'math.' prefix needed
pi                  # 3.141592653589793

import numpy as np  # conventional alias

A module is a first-class object: attributes can be listed and introspected like any other object’s:

import math

type(math)                       # <class 'module'>
"sqrt" in dir(math)               # True
math.__name__                    # 'math'

The name == "main" Pattern

Every module has a name attribute. When a file is run directly, Python sets name to "main"; when the same file is imported, name is set to the module’s own name instead. Guarding script-only code behind this check lets one file work both as an importable module and a runnable script:

# greetings.py
def greet(name: str) -> str:
    return f"Hello, {name}!"

def main() -> None:
    print(greet("world"))

if __name__ == "__main__":
    main()          # runs only when executed as `python greetings.py`
$ python greetings.py
Hello, world!

Importing the same file elsewhere runs the function definitions but skips main(), since name is then "greetings", not "main":

import greetings

greetings.greet("Ada")   # 'Hello, Ada!' -- main() never ran

Packages

A package is a directory containing an init.py file (which may be empty) alongside its modules; init.py marks the directory as importable and runs once, the first time the package is imported. Packages can nest arbitrarily to form subpackages, as shown under Packages in the tutorial:

shapes/
├── __init__.py
├── circle.py
├── square.py
└── solids/
    ├── __init__.py
    └── sphere.py

From outside the package, import a submodule with dotted syntax:

from shapes import circle
from shapes.solids import sphere

circle.Circle(radius=2)
sphere.Sphere(radius=1)

Relative imports, valid only inside a package’s own modules, reference siblings by leading dots — one dot for the current package, two for the parent:

# shapes/square.py
from . import circle          # sibling module in the same package
from .circle import Circle    # a specific name from a sibling module

The Module Search Path

import does not scan the whole filesystem — it searches sys.path, a list built from the script’s own directory, the PYTHONPATH environment variable, and installation-dependent defaults:

import sys

sys.path
# ['', '/usr/lib/python3.13', '/usr/lib/python3.13/site-packages', ...]
$ PYTHONPATH=/opt/mylibs python app.py

Once found, the module’s source is compiled to bytecode and cached under pycache/.pyc; a later import reuses that cache unless the source file is newer, and either way the module body executes *only once per process — a second import of the same name just returns the cached module object without re-running it:

import time

import config   # first import: module body executes, printed side effects appear
import config   # second import: cached module object returned, nothing re-executed

A circular import — module a importing b while b imports a — fails or yields a half-initialized module, because the first import is still running when the second one starts. Avoid it by importing the specific name inside the function that needs it (a local import) rather than at module top level, or by restructuring the shared code into a third module both sides import from.

How import Resolves a Name

flowchart TD A["import name"] --> B{"Already in\nsys.modules?"} B -- Yes --> F["Bind cached module\nobject in caller"] B -- No --> C["Search sys.path\nfor 'name'"] C --> D{"Found?"} D -- No --> E["Raise ModuleNotFoundError"] D -- Yes --> G{".pyc cache\nup to date?"} G -- No --> H["Compile source to .pyc"] G -- Yes --> I["Load cached .pyc"] H --> J["Execute module body once\n(populates sys.modules)"] I --> J J --> F

The exact algorithm — finders, loaders, and the sys.modules cache — is specified in the import system reference; the tutorial’s Modules chapter and its Packages section cover the everyday usage above.

See Also