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.