Python from First PrinciplesPost 9 of 20 · 20 of 20 written

9. Modules, packages, imports and environments

Python · 9 of 203 min readOct 2026

import executes a file once, caches the resulting module object, and binds a name to it. Most “works on my machine” problems in AI are environment problems: the wrong interpreter, the wrong site-packages folder, or the wrong CUDA build.

What import foo does

  1. Check the cache, sys.modules. If foo is there, reuse it; the file is not run again.
  2. Find the file by searching each directory in sys.path: the script’s folder, PYTHONPATH, the standard library, then site-packages.
  3. Execute the file’s top-level code inside a new module object’s namespace.
  4. Bind the name foo in the importing namespace.

Consequences worth remembering:

  • Top-level code runs at import. Loading a model at module level makes every import of that module slow, so put heavy work in functions.
  • Module-level variables are singletons shared by every importer.
  • Circular imports fail when module A uses B’s names before B has finished executing.
  • A file named torch.py, random.py or json.py in your project shadows the real library. When imports behave strangely, print foo.__file__.

if __name__ == "__main__":

A module’s __name__ is "__main__" when run as a script and its dotted name when imported. Guard your entry point so importing the file has no side effects:

def main():
    ...

if __name__ == "__main__":
    main()

The guard is mandatory with multiprocessing under the “spawn” start method (the default on macOS and Windows), because each worker re-imports your main module. PyTorch DataLoader workers count.

Packages and project layout

A package is a directory of modules, usually with an __init__.py that runs when the package is imported. A layout that avoids most import confusion:

my_project/
  pyproject.toml
  src/my_project/
    __init__.py
    data.py
    model.py
    serve.py
  tests/
    test_data.py
  • Absolute imports: from my_project.data import load_dataset.
  • Relative imports inside the package: from .data import load_dataset.
  • Run modules with python -m my_project.serve. Running python src/my_project/serve.py directly often breaks relative imports.

Virtual environments

A virtual environment is a folder with its own link to an interpreter and its own site-packages. Each project gets its own dependency versions without touching the system Python.

  • Create and activate: python -m venv .venv then source .venv/bin/activate.
  • Diagnose which environment you are in: which python and python -c "import sys; print(sys.executable)".
  • Install with python -m pip install ..., never bare pip, so pip belongs to the interpreter you are running.

Package management today

  • pyproject.toml is the standard project file: metadata, dependencies and tool settings (ruff, pytest, mypy) in one place.
  • uv (from Astral) is a fast installer and project manager: uv init, uv add torch, uv run script.py. It writes a lock file and can install Python versions too, and it has become a common default for new projects.
  • pip with requirements.txt is still everywhere. Pin exact versions with == for reproducibility.
  • conda or mamba help when you need non-Python binaries, such as some CUDA toolkits or geospatial libraries.

Libraries declare version ranges (torch>=2.4,<3); applications lock exact versions and commit the lock file.

Wheels, native code and CUDA

  • A wheel (.whl) is a prebuilt package. AI libraries ship compiled C++ and CUDA code inside wheels tied to the OS, CPU architecture, Python version and often the CUDA version.
  • PyTorch publishes separate builds per CUDA version. Installing the wrong one gives you a CPU-only build or a driver mismatch. Verify with torch.__version__, torch.version.cuda and torch.cuda.is_available().
  • The NVIDIA driver must support the CUDA runtime bundled in the wheel. PyTorch wheels usually do not need a separately installed CUDA toolkit.
  • On Apple Silicon, the GPU backend is MPS: check torch.backends.mps.is_available().
  • Docker images pin the whole stack (OS, Python, wheels) so deployment matches development.

Questions to ask:

  • Which interpreter and which site-packages is this actually running?
  • Is anything heavy happening at import time?
  • Is this PyTorch build compiled for a CUDA version this machine’s driver supports?