Intermediate18 min

Modules, packages, and virtual environments

Split code into files, import cleanly, and isolate dependencies so your project is reproducible.

Why this matters in AI / ML / GenAI

"It works on my machine" is the number one ML reproducibility failure. Pinned requirements and a clean package layout are what let a training run reproduce in CI and a Docker image behave like your laptop.

Modules and imports

Every .py file is a module. import chunking then chunking.split(), or from chunking import split.

Keep imports at the top of the file, grouped: standard library, third party, then your own code. Avoid from module import * — it hides where names come from and breaks tooling.

if __name__ == "__main__": guards code that should only run when the file is executed directly, not when it is imported. Put your CLI entry point there so importing the module for tests does not kick off a training run.

A layout that scales

A workable project structure:

myproject/
  src/myproject/__init__.py
  src/myproject/data.py
  src/myproject/model.py
  src/myproject/api.py
  tests/test_data.py
  requirements.txt
  pyproject.toml
  README.md

__init__.py marks a package. Import as from myproject.data import load_rows. Flat repos with twenty files in the root become unnavigable within a month.

Virtual environments and pinning

A virtual environment is a per-project Python with its own packages, so project A on torch 2.1 does not break project B on torch 2.4.

python -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate
pip install -r requirements.txt
pip freeze > requirements.txt

Pin versions (pandas==2.2.2, not pandas). Unpinned dependencies mean a rebuild three months later silently installs a new version and your metrics move. Modern teams use uv or Poetry, but the principle is identical: a lockfile, committed.

Copy-paste examples

Copy into your own editor, or load one into the compiler below and press Run.

The main guard

Importing this file defines the function; running it executes the demo.

def preprocess(text):
    return text.strip().lower()

def main():
    samples = ["  Hello ", "WORLD  "]
    for s in samples:
        print(repr(s), "->", repr(preprocess(s)))

if __name__ == "__main__":
    main()

Import order convention

Standard library, blank line, third party, blank line, local imports.

import json
import logging
from pathlib import Path

# third-party (not installed in this browser sandbox, shown for shape)
# import numpy as np
# import pandas as pd

# local
# from myproject.data import load_rows

logging.basicConfig(level=logging.INFO)
logging.info("imports grouped: stdlib, third-party, local")
print("config path:", Path("configs") / "train.json")
print(json.dumps({"ok": True}))

requirements.txt with pinned versions

Copy this shape into your own project. Exact versions, one per line.

# requirements.txt
# numpy==2.1.3
# pandas==2.2.3
# scikit-learn==1.5.2
# fastapi==0.115.5
# uvicorn==0.32.1
# pydantic==2.10.3
# python-dotenv==1.0.1

print("Pin every dependency. Commit the file. Rebuild reproducibly.")

Inspect the runtime and stdlib

Try it — in-browser Python

Try importing another stdlib module such as random or datetime.

Output

Python runs in your browser. First run downloads the runtime.

Press Run (or Ctrl+Enter) to execute.

CPython in WebAssembly. Stdlib works. NumPy and pandas load on demand. No input(), no GPU, no network installs.

Takeaways

  • Imports go at the top, grouped stdlib / third-party / local. Never import *.
  • Guard entry points with if __name__ == "__main__".
  • One virtual environment per project, and pin every version.